diff --git a/.github/ISSUE_TEMPLATE/comic-source-issues.md b/.github/ISSUE_TEMPLATE/comic-source-issues.md new file mode 100644 index 00000000..609b66fd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/comic-source-issues.md @@ -0,0 +1,39 @@ +--- +name: comic source issues +about: 漫画源问题. +title: "[CS]" +labels: 漫画源问题 +assignees: '' + +--- + +# 这是一个漫画源问题 + +> 漫画源工作情况可以在[project](https://github.com/feilongfl/Cimoc/projects/2)中进行查看,请尽量不要重复 +> 请您在提交本问题前仔细完成下面问题以便更好地解决问题. +> 如果有多个漫画源出错,请不要合并在一起,谢谢 + +## 出现错误的漫画源 + +## 您搜索/观看的漫画名称或关键字以及访问链接 + +## 在以下环节中出现错误 +- [ ] 搜索 +- [ ] 章节信息 +- [ ] 图片加载 + +## 你是否使用科学上网工具 +- [ ] 是 +- [ ] 否 + +## 你是否能够访问漫画源网页并成功加载漫画图片 +- [ ] 是 +- [ ] 否 + +## 您手机的系统版本 +- [ ] 原生(AOSP/LL/RR/Mokee等) +- [ ] 国产(小米/氢氧/锤子) +具体版本: + +## 您使用的cimoc版本 +V1.4.8.8xxx diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..33092f23 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,55 @@ +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale +daysUntilStale: 60 + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. +daysUntilClose: 7 + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable +exemptLabels: + - pinned + - security + - "[Status] Maybe Later" + +# Set to true to ignore issues in a project (defaults to false) +exemptProjects: false + +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: false + +# Label to use when marking as stale +staleLabel: wontfix + +# Comment to post when marking as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. + +# Comment to post when closing a stale Issue or Pull Request. +# closeComment: > +# Your comment here. + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 30 + +# Limit to only `issues` or `pulls` +# only: issues + +# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': +# pulls: +# daysUntilStale: 30 +# markComment: > +# This pull request has been automatically marked as stale because it has not had +# recent activity. It will be closed if no further activity occurs. Thank you +# for your contributions. + +# issues: +# exemptLabels: +# - confirmed \ No newline at end of file diff --git a/.gitignore b/.gitignore index c19fe0c2..76678c9a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,17 +8,17 @@ build/ # Intellij *.iml -.idea/workspace.xml -.idea/libraries +.idea/ # Local configuration file (sdk path, etc) local.properties +/keystore.properties # Android Studio captures folder captures/ # Keystore files -*.jks +# *.jks # Android Studio Navigation editor temp files .navigation/ @@ -27,3 +27,5 @@ captures/ *.class +/gradle.properties +/app/src/main/BrowserFilter-data.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index febc7803..00000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -Cimoc \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 96cc43ef..00000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf33..00000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/dictionaries/Hiroshi.xml b/.idea/dictionaries/Hiroshi.xml deleted file mode 100644 index fc2136d2..00000000 --- a/.idea/dictionaries/Hiroshi.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - cctuku - chuiyao - kami - machi - manhua - shtml - tuku - unfavorite - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 97626ba4..00000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index d52450e7..00000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index fbb68289..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 4e2e6b90..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460d..00000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..d7f5b6c9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,47 @@ +language: android +android: + components: + - tools + - platform-tools + - build-tools-29.0.3 + - android-29 + - add-on + - extra +licenses: +- android-sdk-license-.+ +before_install: +- echo $TRAVIS_BUILD_NUMBER +- yes | sdkmanager "platforms;android-29" +jdk: +- oraclejdk8 +before_script: +- chmod +x gradlew +script: +#- "./gradlew assembleRelease" +- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then ./gradlew assembleDebug; fi' +- 'if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then ./gradlew assembleRelease; fi' +before_deploy: + - echo gitVersion + - git rev-list --all --count + - echo version + - git describe --tags + - echo $TRAVIS_BUILD_NUMBER +deploy: + provider: releases + api_key: + secure: SVka7J99K5/DGL8LVEEnMqX5vIAVw6/bUDJbC41TdKASfWR3Huy97I8xkwiJ947b2eGIlmwVJLR9GD6akeVaUUvpTKr0PXAm7nMdFrkfAaN5ovruqrcnjIYQ/Y0u3yj2JESAuQIpR4ggJ9xWu1OwVfDpDfIFCsLlAKMyok+Bc5/J3SCnAfly0X61e+rs+223pc8GW3Dvaqp/cMo5OiCfhebvVMmXIAENjvKYdciTnxjz5DRzsBDwzSZW/mRn96wgdroGOuoBYUll8EOoHD/yPCbmrRZcvk/DCvRojvwMmOvP7g38DcIbmvYr04MKnHyfvkAWNZNR0k2k5IIe+b2j7HPHqv6g6LN2nysarjW0kA+EzMq9R8lyaLwY1umzs3iavc/BQBVYggP8ItN4cO1gAfefm2dB8Wt9cH7Sr3OJpvQPNDMS1RNhjl0Xo9fR5uLy0BN7n31f3OcChfyOpsM0zFUZGvL7WrxMtABH6lG9DuCzvXvLaThsZQAXNcZyzrwdQMK2YVd//WUuOSiLwq207fa2hadV6q+PpEsX4M1ugOB4Z7FyQ0Se9kzYPgXKrfdtOvbt2yeLYUpUBW6qh+rQ7JMhRG0xK9x9MYZdmf096+0HqC+CVM4/zE0+dpnc8fagL44Wfrs825kUI7sBpgKp6cJQNGSlrZCCda/sJbmddQo= + file: "app/build/outputs/apk/release/Cimoc.apk" + skip_cleanup: true + on: + repo: feilongfl/Cimoc + branch: + - release-tci + - dev + tags: true +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/f2c42f2c1f06e4ac7bd0 + on_success: always # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: always # options: [always|never|change] default: always \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index f4768cd9..1aee2a04 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,90 @@ + + # 应用简介 - -Android 平台在线看漫画的应用 -# 支持网站 -- [看漫画](http://m.ikanman.com) -- [动漫之家](http://m.dmzj.com) -- [汗汗漫画](http://hhaazz.com) -- [CC图库](http://m.tuku.cc) -- ~~E绅士~~ +Android 平台在线漫画阅读器   +Online manga reader based on Android + +[![Build Status](https://travis-ci.org/feilongfl/Cimoc.svg?branch=release-tci)](https://travis-ci.org/feilongfl/Cimoc) +[![codebeat badge](https://codebeat.co/badges/a22ca260-494d-4be8-9e3d-fc9c8f7d0f73)](https://codebeat.co/projects/github-com-feilongfl-cimoc-release-tci) +[![GitHub release](https://img.shields.io/github/release/feilongfl/Cimoc.svg)](https://github.com/feilongfl/Cimoc/releases) +[![Join the chat at https://gitter.im/flcimoc/Lobby](https://badges.gitter.im/flcimoc/Lobby.svg)](https://gitter.im/flcimoc/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![](https://img.shields.io/github/downloads/feilongfl/cimoc/total.svg)](https://github.com/feilongfl/Cimoc/releases) + +# 下载 +> 所有release由`travis-ci`编译发布,如果在release界面某个版本没有apk,那么要么是正在编译,要么就是编译失败了 +> 使用`pre-release`版本会在每次启动时显示检查更新提示。 + +# 漫画源 +> 漫画源工作情况可以在[project](https://github.com/feilongfl/Cimoc/projects/2)中进行查看,请尽量不要重复issues +> 各位大佬们提交漫画源相关issue请按照[模板](https://github.com/feilongfl/Cimoc/issues/new?assignees=&labels=%E6%BC%AB%E7%94%BB%E6%BA%90%E9%97%AE%E9%A2%98&template=comic-source-issues.md&title=%5BCS%5D)填写,方便检查问题。 + +# 功能简介 +- 翻页阅读(Page Reader) +- 卷纸阅读(Scroll Reader) +- 检查漫画更新(Check Manga For Update) +- 下载漫画(Download Manga) +- 本地漫画(Local Reader) +- 本地备份恢复(Local Backup) + +# 感谢以下的开源项目及作者 +- [Android Open Source Project](http://source.android.com/) +- [ButterKnife](https://github.com/JakeWharton/butterknife) +- [GreenDAO](https://github.com/greenrobot/greenDAO) +- [OkHttp](https://github.com/square/okhttp) +- [Fresco](https://github.com/facebook/fresco) +- [Jsoup](https://github.com/jhy/jsoup) +- [DiscreteSeekBar](https://github.com/AnderWeb/discreteSeekBar) +- [RxJava](https://github.com/ReactiveX/RxJava) +- [RxAndroid](https://github.com/ReactiveX/RxAndroid) +- [RecyclerViewPager](https://github.com/lsjwzh/RecyclerViewPager) +- [PhotoDraweeView](https://github.com/ongakuer/PhotoDraweeView) +- [Rhino](https://github.com/mozilla/rhino) +- [BlazingChain](https://github.com/tommyettinger/BlazingChain) + +# ToDo +- 要是能多线程检查更新就好了,我本地收藏两百多,每次检查更新要好久 # 应用截图 - - - - - + + +# 增加图源(欢迎pr) +- 继承 MangaParser 类,参照 Parser 接口的注释 +> 在app\src\main\java\com\hiroshi\cimoc\source目录里面随便找一个复制一下 +> 注释是这个:app\src\main\java\com\hiroshi\cimoc\parser\MangaParser.java +- (可选)继承 MangaCategory 类,参照 Category 接口的注释 +> 这个没什么大用的感觉,个人不常用,直接删掉不会有什么影响 +- 在 SourceManger 的 getParser() 方法中加入相应分支 +> case 里面无脑添加 +- 在 UpdateHelper 的 initSource() 方法中初始化图源 +> 同上 +- (可选)在BrowserFilter中registUrlListener添加相应type,实现关联浏览器操作 +> 在app\src\main\java\com\hiroshi\cimoc\ui\activity\BrowserFilter.java中 +> 修改后运行app\src\main\GenAndroidManifest.fish,使用自动生成的BrowserFilter-data.xml替换AndroidManifest.xml中相应部分 + +# eink设备推荐配置 +## cimoc设置 +- 阅读模式-翻页模式 +- 定义点击事件-左上-上一页,右下下一页 +- 启用快速翻页(减少) +- 自动裁剪百边 +- 禁止双击放大 +- 启用通知栏 +## boox优化配置 +- dpi-424 +- 字体加粗- +- a2刷新-打开 +- 动画过滤200 +- 全刷触发20 +- 全屏-关闭 + +# 控制器使用说明 +> 如果嫌举着平板太累,可以把平板放支架上,用手柄翻页 :smile: +> 不过暂时只能在阅读界面使用 + +我用的是xbox手柄开发的(蓝色的那个,不过貌似颜色没什么关系) +默认b返回左右肩键和扳机键翻页 + +# 关于淘宝售卖和会员破解 +请二次开发软件遵守软件许可证。 +本程序没有任何破解网站VIP的功能,仅仅作为网页浏览器显示网站免费浏览部分,淘宝卖家自行添加的破解或其他功能与本程序无任何关系。 diff --git a/app/.gitignore b/app/.gitignore index 796b96d1..0ea7202d 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1 +1,3 @@ /build +/release +/schemas \ No newline at end of file diff --git a/app/.key.jks b/app/.key.jks new file mode 100644 index 00000000..6b69c2f8 Binary files /dev/null and b/app/.key.jks differ diff --git a/app/build.gradle b/app/build.gradle index 54acdedf..87cbf625 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,45 +1,101 @@ -apply plugin: 'org.greenrobot.greendao' apply plugin: 'com.android.application' -apply plugin: 'android-apt' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'org.greenrobot.greendao' + +def buildVersion = 329 +def version = "v1.4.14" + +def cmdGetTagname = 'git describe --tags' +def cmdGetCommitNums = 'git rev-list HEAD --count' +if(System.getenv("TRAVIS_BUILD_NUMBER") != null){ + buildVersion = System.getenv("TRAVIS_BUILD_NUMBER").trim().toInteger() + version = cmdGetTagname.execute().text.trim() +} +else { + buildVersion = cmdGetCommitNums.execute().text.trim().toInteger() +} +version = cmdGetTagname.execute().text.trim() + +apply plugin: 'com.google.gms.google-services' // Google Services plugin +apply plugin: 'com.google.firebase.crashlytics' android { - compileSdkVersion 23 - buildToolsVersion "23.0.3" + compileSdkVersion 29 + buildToolsVersion '29.0.3' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } defaultConfig { applicationId "com.hiroshi.cimoc" - minSdkVersion 15 - targetSdkVersion 23 - versionCode 1 - versionName "1.0.0" + minSdkVersion 16 + targetSdkVersion 29 + versionCode buildVersion + versionName version resConfigs "en", "zh" + multiDexEnabled true + } + signingConfigs { + release { + storeFile file(".key.jks") + storePassword System.getenv("KEYSTORE_PASS") + keyAlias System.getenv("ALIAS_NAME") + keyPassword System.getenv("ALIAS_PASS") + } } buildTypes { release { minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' shrinkResources true - proguardFiles 'proguard-rules.pro' + zipAlignEnabled true + signingConfig signingConfigs.release } + android.applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = "Cimoc.apk" + } + } + } + dexOptions { + additionalParameters = ["--minimal-main-dex"] } } greendao { - schemaVersion 1 + schemaVersion 10 } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:support-v4:23.4.0' - compile 'com.android.support:appcompat-v7:23.4.0' - compile 'com.android.support:recyclerview-v7:23.4.0' - compile 'com.android.support:design:23.4.0' - compile 'com.jakewharton:butterknife:8.1.0' - apt 'com.jakewharton:butterknife-compiler:8.1.0' - compile 'org.greenrobot:eventbus:3.0.0' - compile 'org.greenrobot:greendao:3.0.1' - compile 'com.squareup.okhttp3:okhttp:3.4.1' - compile 'com.facebook.fresco:fresco:0.11.0' - compile 'com.facebook.fresco:imagepipeline-okhttp3:0.11.0' - compile 'org.jsoup:jsoup:1.9.2' - compile 'org.adw.library:discrete-seekbar:1.0.1' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'androidx.legacy:legacy-support-v13:1.0.0' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation 'com.google.android.material:material:1.1.0' + implementation 'com.jakewharton:butterknife:10.2.1' + annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0' + implementation 'org.greenrobot:greendao:3.3.0' + //noinspection GradleDependency : for support android low api devices,not update OKHTTP + implementation 'com.squareup.okhttp3:okhttp:3.12.3' + implementation 'com.facebook.fresco:fresco:2.0.0' + implementation 'org.jsoup:jsoup:1.12.1' + implementation 'org.adw.library:discrete-seekbar:1.0.1' + implementation 'io.reactivex:rxjava:1.3.8' + implementation 'io.reactivex:rxandroid:1.2.1' + api 'com.google.guava:guava:27.0.1-android' + implementation 'androidx.multidex:multidex:2.0.1' + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.alibaba:fastjson:1.2.62' + implementation 'com.google.firebase:firebase-analytics:17.2.3' + implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta02' + implementation 'com.google.firebase:firebase-config:19.1.3' + implementation "androidx.core:core-ktx:1.1.0" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} +repositories { + mavenCentral() } diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 00000000..afc1fd28 --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,40 @@ +{ + "project_info": { + "project_number": "271512521468", + "firebase_url": "https://cimoc-11974.firebaseio.com", + "project_id": "cimoc-11974", + "storage_bucket": "cimoc-11974.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:271512521468:android:33b6c6b6fc4513381da579", + "android_client_info": { + "package_name": "com.hiroshi.cimoc" + } + }, + "oauth_client": [ + { + "client_id": "271512521468-ipurfdujhq4lo9j54sdtocqborsjlgkb.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyChjVPhKrCinTfKx7gmJLCB85c1zT5EerY" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "271512521468-ipurfdujhq4lo9j54sdtocqborsjlgkb.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/libs/commons-lang3-3.7.jar b/app/libs/commons-lang3-3.7.jar new file mode 100755 index 00000000..f37ded60 Binary files /dev/null and b/app/libs/commons-lang3-3.7.jar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 54987e15..6cb0efbf 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -16,79 +16,61 @@ # public *; #} --optimizationpasses 8 --dontusemixedcaseclassnames --dontskipnonpubliclibraryclasses --dontpreverify --verbose --optimizations !code/simplification/arithmetic,!field/*,!class/merging/* - --keep public class * extends android.app.Activity --keep public class * extends android.app.Application --keep public class * extends android.app.Service --keep public class * extends android.content.BroadcastReceiver --keep public class * extends android.content.ContentProvider --keep public class * extends android.app.backup.BackupAgentHelper --keep public class * extends android.preference.Preference --keep public class com.android.vending.licensing.ILicensingService - --keepattributes *Annotation* --keepattributes Signature - --keepclasseswithmembernames class * { - native ; -} +-optimizationpasses 5 --keepclasseswithmembers class * { - public (android.content.Context, android.util.AttributeSet); -} +-keepattributes SourceFile, LineNumberTable --keepclasseswithmembers class * { - public (android.content.Context, android.util.AttributeSet, int); -} - --keepclassmembers class * extends android.app.Activity { - public void *(android.view.View); -} +# fresco +# Keep our interfaces so they can be used by other ProGuard rules. +# See http://sourceforge.net/p/proguard/bugs/466/ +-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip +-keep,allowobfuscation @interface com.facebook.soloader.DoNotOptimize --keepclassmembers enum * { - public static **[] values(); - public static ** valueOf(java.lang.String); +# Do not strip any method/class that is annotated with @DoNotStrip +-keep @com.facebook.common.internal.DoNotStrip class * +-keepclassmembers class * { + @com.facebook.common.internal.DoNotStrip *; } --keep class * implements android.os.Parcelable { - public static final android.os.Parcelable$Creator *; +# Do not strip any method/class that is annotated with @DoNotOptimize +-keep @com.facebook.soloader.DoNotOptimize class * +-keepclassmembers class * { + @com.facebook.soloader.DoNotOptimize *; } -# fresco +# Keep native methods -keepclassmembers class * { native ; } --keep @com.facebook.common.internal.DoNotStrip class * --keepclassmembers class * { - @com.facebook.common.internal.DoNotStrip *; + +# Do not strip SoLoader class and init method +-keep public class com.facebook.soloader.SoLoader { + public static void init(android.content.Context, int); } + -dontwarn okio.** +-dontwarn com.squareup.okhttp.** +-dontwarn okhttp3.** -dontwarn javax.annotation.** - -# EventBus --keepclassmembers class ** { - @org.greenrobot.eventbus.Subscribe ; -} --keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent { - (java.lang.Throwable); -} --keep enum org.greenrobot.eventbus.ThreadMode { *; } +-dontwarn com.android.volley.toolbox.** +-dontwarn com.facebook.infer.** # greenDAO -keepclassmembers class * extends org.greenrobot.greendao.AbstractDao { - public static java.lang.String TABLENAME; +public static java.lang.String TABLENAME; } --keep class **$Properties +#ref: https://juejin.im/post/5d5fb53b51882554a13f8b6a +#-keep class **$Properties +-keep class **$Properties{*;} -dontwarn org.greenrobot.greendao.database.** +-dontwarn org.greenrobot.greendao.rx.** # ButterKnife --keep public class * implements butterknife.internal.ViewBinder { public (); } +# Retain generated class which implement Unbinder. +-keep public class * implements butterknife.Unbinder { public (**, android.view.View); } + +# Prevent obfuscation of types which use ButterKnife annotations since the simple name +# is used to reflectively look up the generated ViewBinding. -keep class butterknife.* -keepclasseswithmembernames class * { @butterknife.* ; } -keepclasseswithmembernames class * { @butterknife.* ; } @@ -108,4 +90,45 @@ # andrroid v4 v7 -dontwarn android.support.v4.** --dontwarn android.support.v7.** \ No newline at end of file +-dontwarn android.support.v7.** + +# rx +-dontwarn sun.misc.** +-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { + long producerIndex; + long consumerIndex; +} +-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { + rx.internal.util.atomic.LinkedQueueNode producerNode; +} +-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { + rx.internal.util.atomic.LinkedQueueNode consumerNode; +} + +#mongodb +-dontwarn javax.** +-dontwarn java.lang.management.** +-dontwarn io.netty.** +-dontwarn org.ietf.jgss.** +-dontwarn org.slf4j.** +-dontwarn org.xerial.snappy.** + +-keep class javax.** { *; } +-keep class java.lang.management.** { *; } +-keep class io.netty.** { *; } +-keep class org.ietf.jgss.** { *; } +-keep class org.slf4j.** { *; } +-keep class org.xerial.snappy.** { *; } + +# guava +-dontwarn com.google.common.base.** +-keep class com.google.common.base.** {*;} +-dontwarn com.google.errorprone.annotations.** +-keep class com.google.errorprone.annotations.** {*;} +-dontwarn com.google.j2objc.annotations.** +-keep class com.google.j2objc.annotations.** { *; } +-dontwarn java.lang.ClassValue +-keep class java.lang.ClassValue { *; } +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement +-keep class org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement { *; } + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b64a9487..8b435cf1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,39 +1,274 @@ + - + + - + android:usesCleartextTraffic="true" + android:largeHeap="true" + android:requestLegacyExternalStorage="true" + android:theme="@style/AppTheme" + tools:ignore="UnusedAttribute"> + android:screenOrientation="unspecified" + android:windowSoftInputMode="adjustPan"> - - + + + - - + android:screenOrientation="unspecified" /> + android:screenOrientation="unspecified" /> + + + + + + + + + + + + + + + + + + android:name=".ui.activity.BrowserFilter" + android:theme="@android:style/Theme.NoDisplay"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/app/src/main/GenAndroidManifest.fish b/app/src/main/GenAndroidManifest.fish new file mode 100644 index 00000000..4f4b2931 --- /dev/null +++ b/app/src/main/GenAndroidManifest.fish @@ -0,0 +1,10 @@ +#!/usr/bin/env fish + +set File BrowserFilter-data.xml +echo '' | tee $File + +for url in (cat java/com/hiroshi/cimoc/source/*.java | grep "new UrlFilter" | perl -pe 's|.*?"(.*?)".*|\1|'); + echo '' | tee -a $File +end + +echo '' | tee -a $File diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png new file mode 100644 index 00000000..f254c66c Binary files /dev/null and b/app/src/main/ic_launcher-web.png differ diff --git a/app/src/main/java/com/hiroshi/cimoc/App.java b/app/src/main/java/com/hiroshi/cimoc/App.java new file mode 100644 index 00000000..b71c31e3 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/App.java @@ -0,0 +1,291 @@ +package com.hiroshi.cimoc; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.os.Bundle; +import androidx.multidex.MultiDex; +import androidx.recyclerview.widget.RecyclerView; +import android.util.DisplayMetrics; +import android.view.WindowManager; + +import com.facebook.drawee.backends.pipeline.Fresco; +import com.hiroshi.cimoc.component.AppGetter; +import com.hiroshi.cimoc.core.Storage; +import com.hiroshi.cimoc.fresco.ControllerBuilderProvider; +import com.hiroshi.cimoc.helper.DBOpenHelper; +import com.hiroshi.cimoc.helper.UpdateHelper; +import com.hiroshi.cimoc.manager.PreferenceManager; +import com.hiroshi.cimoc.manager.SourceManager; +import com.hiroshi.cimoc.misc.ActivityLifecycle; +import com.hiroshi.cimoc.model.DaoMaster; +import com.hiroshi.cimoc.model.DaoSession; +import com.hiroshi.cimoc.saf.DocumentFile; +import com.hiroshi.cimoc.ui.adapter.GridAdapter; +import com.hiroshi.cimoc.utils.DocumentUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.greenrobot.greendao.identityscope.IdentityScopeType; + +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import okhttp3.OkHttpClient; +import androidx.multidex.MultiDexApplication; + +/** + * Created by Hiroshi on 2016/7/5. + */ +public class App extends MultiDexApplication implements AppGetter, Thread.UncaughtExceptionHandler { + + public static int mWidthPixels; + public static int mHeightPixels; + public static int mCoverWidthPixels; + public static int mCoverHeightPixels; + public static int mLargePixels; + + private static OkHttpClient mHttpClient; + + private DocumentFile mDocumentFile; + private static PreferenceManager mPreferenceManager; + private ControllerBuilderProvider mBuilderProvider; + private RecyclerView.RecycledViewPool mRecycledPool; + private DaoSession mDaoSession; + private ActivityLifecycle mActivityLifecycle; + + + private static WifiManager manager_wifi; + private static App mApp; + private static Activity sActivity; + + // 默认Github源 + private static String UPDATE_CURRENT_URL = "https://api.github.com/repos/feilongfl/Cimoc/releases/latest"; + + @Override + public void onCreate() { + super.onCreate(); + Thread.setDefaultUncaughtExceptionHandler(this); + mActivityLifecycle = new ActivityLifecycle(); + registerActivityLifecycleCallbacks(mActivityLifecycle); + mPreferenceManager = new PreferenceManager(this); + DBOpenHelper helper = new DBOpenHelper(this, "cimoc.db"); + mDaoSession = new DaoMaster(helper.getWritableDatabase()).newSession(IdentityScopeType.None); + UpdateHelper.update(mPreferenceManager, getDaoSession()); + Fresco.initialize(this); + initPixels(); + + manager_wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE); + //获取栈顶Activity以及当前App上下文 + mApp = this; + this.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { +// Log.d("ActivityLifecycle:",activity+"onActivityCreated"); + } + + @Override + public void onActivityStarted(Activity activity) { +// Log.d("ActivityLifecycle:",activity+"onActivityStarted"); + sActivity = activity; + + } + + @Override + public void onActivityResumed(Activity activity) { + + } + + @Override + public void onActivityPaused(Activity activity) { + + } + + @Override + public void onActivityStopped(Activity activity) { + + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + + } + + @Override + public void onActivityDestroyed(Activity activity) { + + } + }); + } + + @Override + public void uncaughtException(Thread t, Throwable e) { + StringBuilder sb = new StringBuilder(); + sb.append("MODEL: ").append(Build.MODEL).append('\n'); + sb.append("SDK: ").append(Build.VERSION.SDK_INT).append('\n'); + sb.append("RELEASE: ").append(Build.VERSION.RELEASE).append('\n'); + sb.append('\n').append(e.getLocalizedMessage()).append('\n'); + for (StackTraceElement element : e.getStackTrace()) { + sb.append('\n'); + sb.append(element.toString()); + } + try { + DocumentFile doc = getDocumentFile(); + DocumentFile dir = DocumentUtils.getOrCreateSubDirectory(doc, "log"); + DocumentFile file = DocumentUtils.getOrCreateFile(dir, StringUtils.getDateStringWithSuffix("log")); + DocumentUtils.writeStringToFile(getContentResolver(), file, sb.toString()); + } catch (Exception ex) { + } + mActivityLifecycle.clear(); + System.exit(1); + } + + @Override + public App getAppInstance() { + return this; + } + + public static Context getAppContext() { + return mApp; + } + + public static Resources getAppResources() { + return mApp.getResources(); + } + + public static Activity getActivity() { + return sActivity; + } + + public static WifiManager getManager_wifi() { + return manager_wifi; + } + + private void initPixels() { + DisplayMetrics metrics = new DisplayMetrics(); + ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metrics); + mWidthPixels = metrics.widthPixels; + mHeightPixels = metrics.heightPixels; + mCoverWidthPixels = mWidthPixels / 3; + mCoverHeightPixels = mHeightPixels * mCoverWidthPixels / mWidthPixels; + mLargePixels = 3 * metrics.widthPixels * metrics.heightPixels; + } + + public void initRootDocumentFile() { + String uri = mPreferenceManager.getString(PreferenceManager.PREF_OTHER_STORAGE); + mDocumentFile = Storage.initRoot(this, uri); + } + + public DocumentFile getDocumentFile() { + if (mDocumentFile == null) { + initRootDocumentFile(); + } + return mDocumentFile; + } + + public DaoSession getDaoSession() { + return mDaoSession; + } + + public static PreferenceManager getPreferenceManager() { + return mPreferenceManager; + } + + public RecyclerView.RecycledViewPool getGridRecycledPool() { + if (mRecycledPool == null) { + mRecycledPool = new RecyclerView.RecycledViewPool(); + mRecycledPool.setMaxRecycledViews(GridAdapter.TYPE_GRID, 20); + } + return mRecycledPool; + } + + public ControllerBuilderProvider getBuilderProvider() { + if (mBuilderProvider == null) { + mBuilderProvider = new ControllerBuilderProvider(getApplicationContext(), + SourceManager.getInstance(this).new HeaderGetter(), true); + } + return mBuilderProvider; + } + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + MultiDex.install(this); + } + + public static void setUpdateCurrentUrl(String updateCurrentUrl) { + UPDATE_CURRENT_URL = updateCurrentUrl; + } + + public static String getUpdateCurrentUrl() { + return UPDATE_CURRENT_URL; + } + + public static OkHttpClient getHttpClient() { + + //OkHttpClient返回null实现"仅WiFi联网",后面要注意空指针处理 + if (!manager_wifi.isWifiEnabled() && mPreferenceManager.getBoolean(PreferenceManager.PREF_OTHER_CONNECT_ONLY_WIFI, false)) { + return null; + } + + if (mHttpClient == null) { + + // 3.OkHttp访问https的Client实例 + mHttpClient = new OkHttpClient().newBuilder() + .sslSocketFactory(createSSLSocketFactory()) + .hostnameVerifier(new TrustAllHostnameVerifier()) + .build(); + } + + return mHttpClient; + } + + // 1.实现X509TrustManager接口 + private static class TrustAllCerts implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } + + // 2.实现HostnameVerifier接口 + private static class TrustAllHostnameVerifier implements HostnameVerifier { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + } + + private static SSLSocketFactory createSSLSocketFactory() { + SSLSocketFactory ssfFactory = null; + + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, new TrustManager[]{new TrustAllCerts()}, new SecureRandom()); + + ssfFactory = sc.getSocketFactory(); + } catch (Exception e) { + } + + return ssfFactory; + } + + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/CimocApplication.java b/app/src/main/java/com/hiroshi/cimoc/CimocApplication.java deleted file mode 100644 index 9a47f7d5..00000000 --- a/app/src/main/java/com/hiroshi/cimoc/CimocApplication.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.hiroshi.cimoc; - -import android.app.Application; -import android.content.Context; -import android.content.SharedPreferences; - -import com.facebook.drawee.backends.pipeline.Fresco; -import com.hiroshi.cimoc.model.DaoMaster; -import com.hiroshi.cimoc.model.DaoMaster.DevOpenHelper; -import com.hiroshi.cimoc.model.DaoSession; - -import org.greenrobot.greendao.database.Database; - -import okhttp3.OkHttpClient; - -/** - * Created by Hiroshi on 2016/7/5. - */ -public class CimocApplication extends Application { - - public static final String PREF_EX = "pref_ex"; - - private static final String PREFERENCES_NAME = "cimoc_preferences"; - - private static DaoSession daoSession; - private static OkHttpClient httpClient; - private static SharedPreferences preferences; - - @Override - public void onCreate() { - super.onCreate(); - initDatabase(); - preferences = getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); - Fresco.initialize(this); -// ExLog.enable(); - } - - private void initDatabase() { - DevOpenHelper helper = new DevOpenHelper(this, "cimoc.db"); - Database db = helper.getWritableDb(); - daoSession = new DaoMaster(db).newSession(); - } - - public static DaoSession getDaoSession() { - return daoSession; - } - - public static SharedPreferences getPreferences() { - return preferences; - } - - public static OkHttpClient getHttpClient() { - if (httpClient == null) { - httpClient = new OkHttpClient(); - } - return httpClient; - } - -} diff --git a/app/src/main/java/com/hiroshi/cimoc/Constants.java b/app/src/main/java/com/hiroshi/cimoc/Constants.java new file mode 100644 index 00000000..00ebe22d --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/Constants.java @@ -0,0 +1,10 @@ +package com.hiroshi.cimoc; + +public final class Constants { + + public static final int VERSION = 105006; + + public static final String UPDATE_GITHUB_URL = "https://api.github.com/repos/feilongfl/Cimoc/releases/latest"; + + public static final String UPDATE_GITEE_URL = "https://gitee.com/api/v5/repos/feilongfl/Cimoc_Mirror/releases/latest"; +} diff --git a/app/src/main/java/com/hiroshi/cimoc/component/AppGetter.java b/app/src/main/java/com/hiroshi/cimoc/component/AppGetter.java new file mode 100644 index 00000000..dff709a6 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/component/AppGetter.java @@ -0,0 +1,13 @@ +package com.hiroshi.cimoc.component; + +import com.hiroshi.cimoc.App; + +/** + * Created by Hiroshi on 2017/1/21. + */ + +public interface AppGetter { + + App getAppInstance(); + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/component/DialogCaller.java b/app/src/main/java/com/hiroshi/cimoc/component/DialogCaller.java new file mode 100644 index 00000000..53e5d302 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/component/DialogCaller.java @@ -0,0 +1,23 @@ +package com.hiroshi.cimoc.component; + +import android.os.Bundle; + +/** + * Created by Hiroshi on 2016/12/4. + */ + +public interface DialogCaller { + + String EXTRA_DIALOG_RESULT_INDEX = "cimoc.intent.extra.EXTRA_DIALOG_RESULT_INDEX"; + String EXTRA_DIALOG_RESULT_VALUE = "cimoc.intent.extra.EXTRA_DIALOG_RESULT_VALUE"; + String EXTRA_DIALOG_REQUEST_CODE = "cimoc.intent.extra.EXTRA_DIALOG_REQUEST_CODE"; + String EXTRA_DIALOG_TITLE = "cimoc.intent.extra.EXTRA_DIALOG_TITLE"; + String EXTRA_DIALOG_ITEMS = "cimoc.intent.extra.EXTRA_DIALOG_ITEMS"; + String EXTRA_DIALOG_CONTENT = "cimoc.intent.extra.EXTRA_DIALOG_CONTENT"; + String EXTRA_DIALOG_CONTENT_TEXT = "cimoc.intent.extra.EXTRA_DIALOG_CONTENT_TEXT"; + String EXTRA_DIALOG_NEGATIVE = "cimoc.intent.extra.EXTRA_DIALOG_NEGATIVE"; + String EXTRA_DIALOG_CHOICE_ITEMS = "cimoc.intent.extra.EXTRA_DIALOG_CHOICE_ITEMS"; + + void onDialogResult(int requestCode, Bundle bundle); + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/component/ThemeResponsive.java b/app/src/main/java/com/hiroshi/cimoc/component/ThemeResponsive.java new file mode 100644 index 00000000..1280f207 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/component/ThemeResponsive.java @@ -0,0 +1,13 @@ +package com.hiroshi.cimoc.component; + +import androidx.annotation.ColorRes; + +/** + * Created by Hiroshi on 2016/12/2. + */ + +public interface ThemeResponsive { + + void onThemeChange(@ColorRes int primary, @ColorRes int accent); + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/Backup.java b/app/src/main/java/com/hiroshi/cimoc/core/Backup.java new file mode 100644 index 00000000..8acca609 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/core/Backup.java @@ -0,0 +1,353 @@ +package com.hiroshi.cimoc.core; + +import android.content.ContentResolver; +import android.util.Pair; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.google.gson.Gson; +import com.hiroshi.cimoc.App; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.Tag; +import com.hiroshi.cimoc.saf.DocumentFile; +import com.hiroshi.cimoc.utils.DocumentUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import rx.Observable; +import rx.Subscriber; +import rx.schedulers.Schedulers; + +/** + * Created by Hiroshi on 2016/7/22. + */ +public class Backup { + + private static final String BACKUP = "backup"; + + // before 1.4.3 + private static final String SUFFIX_CIMOC = "cimoc"; + + // cfbf = Cimoc Favorite Backup File + private static final String SUFFIX_CFBF = "cfbf"; + + // ctbf = Cimoc Tag Backup File + private static final String SUFFIX_CTBF = "ctbf"; + + // csbf = Cimoc Settings Backup File + private static final String SUFFIX_CSBF = "csbf"; + + private static final String JSON_CIMOC_KEY_COMIC_SOURCE = "s"; + private static final String JSON_CIMOC_KEY_COMIC_CID = "i"; + private static final String JSON_CIMOC_KEY_COMIC_TITLE = "t"; + private static final String JSON_CIMOC_KEY_COMIC_COVER = "c"; + private static final String JSON_CIMOC_KEY_COMIC_UPDATE = "u"; + private static final String JSON_CIMOC_KEY_COMIC_FINISH = "f"; + private static final String JSON_CIMOC_KEY_COMIC_LAST = "l"; + private static final String JSON_CIMOC_KEY_COMIC_PAGE = "p"; + + private static final String JSON_KEY_VERSION = "version"; + private static final String JSON_KEY_TAG_ARRAY = "tag"; + private static final String JSON_KEY_TAG_TITLE = "title"; + private static final String JSON_KEY_COMIC_ARRAY = "comic"; + private static final String JSON_KEY_COMIC_SOURCE = "source"; + private static final String JSON_KEY_COMIC_CID = "cid"; + private static final String JSON_KEY_COMIC_TITLE = "title"; + private static final String JSON_KEY_COMIC_COVER = "cover"; + private static final String JSON_KEY_COMIC_UPDATE = "update"; + private static final String JSON_KEY_COMIC_FINISH = "finish"; + private static final String JSON_KEY_COMIC_LAST = "last"; + private static final String JSON_KEY_COMIC_PAGE = "page"; + private static final String JSON_KEY_COMIC_CHAPTER = "chapter"; + private static final String JSON_KEY_COMIC_FAVORITE = "favorite"; + private static final String JSON_KEY_COMIC_HISTORY = "history"; + + public static Observable loadFavorite(DocumentFile root) { + return load(root, SUFFIX_CIMOC, SUFFIX_CFBF); + } + + public static Observable loadTag(DocumentFile root) { + return load(root, SUFFIX_CTBF); + } + + public static Observable loadSettings(DocumentFile root) { + return load(root, SUFFIX_CSBF); + } + + private static Observable load(final DocumentFile root, final String... suffix) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + DocumentFile dir = DocumentUtils.getOrCreateSubDirectory(root, BACKUP); + if (dir != null) { + String[] files = DocumentUtils.listFilesWithSuffix(dir, suffix); + if (files.length != 0) { + Arrays.sort(files); + subscriber.onNext(files); + subscriber.onCompleted(); + } + } + subscriber.onError(new Exception()); + } + }).subscribeOn(Schedulers.io()); + } + + public static void saveComicAuto(ContentResolver resolver, DocumentFile root, List list) { + DocumentFile dir = DocumentUtils.getOrCreateSubDirectory(root, BACKUP); + if (dir != null) { + try { + JSONObject result = new JSONObject(); + result.put(JSON_KEY_VERSION, 1); + result.put(JSON_KEY_COMIC_ARRAY, buildComicArray(list)); + DocumentFile file = DocumentUtils.getOrCreateFile(dir, "automatic.".concat(SUFFIX_CFBF)); + DocumentUtils.writeStringToFile(resolver, file, result.toString()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public static int saveComic(ContentResolver resolver, DocumentFile root, List list) { + DocumentFile dir = DocumentUtils.getOrCreateSubDirectory(root, BACKUP); + if (dir != null) { + try { + JSONObject result = new JSONObject(); + result.put(JSON_KEY_VERSION, 1); + result.put(JSON_KEY_COMIC_ARRAY, buildComicArray(list)); + String filename = StringUtils.getDateStringWithSuffix(SUFFIX_CFBF); + DocumentFile file = DocumentUtils.getOrCreateFile(dir, filename); + DocumentUtils.writeStringToFile(resolver, file, result.toString()); + return list.size(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return -1; + } + + public static int saveTag(final ContentResolver resolver, final DocumentFile root, final List>> list) { + DocumentFile dir = DocumentUtils.getOrCreateSubDirectory(root, BACKUP); + if (dir != null) { + try { + JSONObject result = new JSONObject(); + result.put(JSON_KEY_VERSION, 2); + result.put(JSON_KEY_TAG_ARRAY, buildTagArray(list)); + String filename = StringUtils.getDateStringWithSuffix(SUFFIX_CTBF); + DocumentFile file = DocumentUtils.getOrCreateFile(dir, filename); + DocumentUtils.writeStringToFile(resolver, file, result.toString()); + return list.size(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return -1; + } + + public static int saveSetting(ContentResolver resolver, DocumentFile root, Map settingMap) { + DocumentFile dir = DocumentUtils.getOrCreateSubDirectory(root, BACKUP); + if (dir != null) { + try { +// String result = XMLUtils.converter(settingMap); +// JSONObject result = new JSONObject(settingMap); + Gson gson = new Gson(); + String result = gson.toJson(settingMap); + + String filename = StringUtils.getDateStringWithSuffix(SUFFIX_CSBF); + DocumentFile file = DocumentUtils.getOrCreateFile(dir, filename); + DocumentUtils.writeStringToFile(resolver, file, result.toString()); + return settingMap.size(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return -1; + } + + private static JSONArray buildTagArray(List>> list) throws JSONException { + JSONArray array = new JSONArray(); + for (Pair> pair : list) { + array.put(buildTagObject(pair.first, pair.second)); + } + return array; + } + + private static JSONObject buildTagObject(Tag tag, List list) throws JSONException { + JSONObject object = new JSONObject(); + object.put(JSON_KEY_TAG_TITLE, tag.getTitle()); + object.put(JSON_KEY_COMIC_ARRAY, buildComicArray(list)); + return object; + } + + private static JSONArray buildComicArray(List list) throws JSONException { + JSONArray array = new JSONArray(); + for (Comic comic : list) { + array.put(buildComicObject(comic)); + } + return array; + } + + private static JSONObject buildComicObject(Comic comic) throws JSONException { + JSONObject object = new JSONObject(); + object.put(JSON_KEY_COMIC_SOURCE, comic.getSource()); + object.put(JSON_KEY_COMIC_CID, comic.getCid()); + object.put(JSON_KEY_COMIC_TITLE, comic.getTitle()); + object.put(JSON_KEY_COMIC_COVER, comic.getCover()); + object.put(JSON_KEY_COMIC_UPDATE, comic.getUpdate()); + object.put(JSON_KEY_COMIC_FINISH, comic.getFinish()); + object.put(JSON_KEY_COMIC_FAVORITE, comic.getFavorite()); + object.put(JSON_KEY_COMIC_HISTORY, comic.getHistory()); + object.put(JSON_KEY_COMIC_LAST, comic.getLast()); + object.put(JSON_KEY_COMIC_PAGE, comic.getPage()); + object.put(JSON_KEY_COMIC_CHAPTER, comic.getChapter()); + return object; + } + + private static String readBackupFile(ContentResolver resolver, DocumentFile root, String filename) { + DocumentFile dir = DocumentUtils.getOrCreateSubDirectory(root, BACKUP); + if (dir != null) { + DocumentFile file = dir.findFile(filename); + return DocumentUtils.readLineFromFile(resolver, file); + } + return null; + } + + public static Observable>>> restoreTag(final ContentResolver resolver, final DocumentFile root, final String filename) { + return Observable.create(new Observable.OnSubscribe>>>() { + @Override + public void call(Subscriber>>> subscriber) { + List>> result = new LinkedList<>(); + String jsonString = readBackupFile(resolver, root, filename); + try { + JSONObject object = new JSONObject(jsonString); + switch (object.getInt(JSON_KEY_VERSION)) { + case 1: + result.add(Pair.create( + new Tag(null, object.getJSONObject(JSON_KEY_TAG_ARRAY).getString(JSON_KEY_TAG_TITLE)), + loadComicArray(object.getJSONArray(JSON_KEY_COMIC_ARRAY), SUFFIX_CTBF))); + break; + case 2: + result.addAll(loadTagArray(object.getJSONArray(JSON_KEY_TAG_ARRAY))); + break; + } + subscriber.onNext(result); + subscriber.onCompleted(); + } catch (JSONException e) { + subscriber.onError(e); + } + } + }).subscribeOn(Schedulers.io()); + } + + public static Observable> restoreComic(final ContentResolver resolver, final DocumentFile root, final String filename) { + return Observable.create(new Observable.OnSubscribe>() { + @Override + public void call(Subscriber> subscriber) { + List list = new LinkedList<>(); + String jsonString = readBackupFile(resolver, root, filename); + try { + if (filename.endsWith(SUFFIX_CIMOC)) { + list.addAll(loadComicArray(new JSONArray(jsonString), SUFFIX_CIMOC)); + } else if (filename.endsWith(SUFFIX_CFBF)) { + JSONObject object = new JSONObject(jsonString); + list.addAll(loadComicArray(object.getJSONArray(JSON_KEY_COMIC_ARRAY), SUFFIX_CFBF)); + } + subscriber.onNext(list); + subscriber.onCompleted(); + } catch (JSONException e) { + subscriber.onError(e); + } + } + }).subscribeOn(Schedulers.io()); + } + + public static Observable> restoreSetting(final ContentResolver resolver, final DocumentFile root, final String filename) { + return Observable.create(new Observable.OnSubscribe>() { + @Override + public void call(Subscriber> subscriber) { + + String jsonString = readBackupFile(resolver, root, filename); + + //将jsonStr转为Map + Map entries = JSON.parseObject( + jsonString, new TypeReference>() { + }); + if (filename.endsWith(SUFFIX_CSBF)) { + for (Map.Entry entry : entries.entrySet()) { + App.getPreferenceManager().putObject(entry.getKey().toString(), entry.getValue()); + } + } + subscriber.onNext(entries); + subscriber.onCompleted(); + } + }).subscribeOn(Schedulers.io()); + } + + private static List>> loadTagArray(JSONArray array) throws JSONException { + List>> list = new LinkedList<>(); + for (int i = 0; i != array.length(); ++i) { + JSONObject object = array.getJSONObject(i); + Tag tag = new Tag(null, object.getString(JSON_KEY_TAG_TITLE)); + list.add(Pair.create(tag, loadComicArray(object.getJSONArray(JSON_KEY_COMIC_ARRAY), SUFFIX_CFBF))); + } + return list; + } + + private static List loadComicArray(JSONArray array, String suffix) throws JSONException { + List list = new LinkedList<>(); + switch (suffix) { + case SUFFIX_CIMOC: + for (int i = 0; i != array.length(); ++i) { + JSONObject object = array.getJSONObject(i); + int source = object.getInt(JSON_CIMOC_KEY_COMIC_SOURCE); + String cid = object.getString(JSON_CIMOC_KEY_COMIC_CID); + String title = object.getString(JSON_CIMOC_KEY_COMIC_TITLE); + String cover = object.getString(JSON_CIMOC_KEY_COMIC_COVER); + String update = object.optString(JSON_CIMOC_KEY_COMIC_UPDATE, null); + Boolean finish = object.has(JSON_CIMOC_KEY_COMIC_FINISH) ? + object.getBoolean(JSON_CIMOC_KEY_COMIC_FINISH) : null; + String last = object.optString(JSON_CIMOC_KEY_COMIC_LAST, null); + Integer page = object.has(JSON_CIMOC_KEY_COMIC_PAGE) ? + object.getInt(JSON_CIMOC_KEY_COMIC_PAGE) : null; + list.add(new Comic(null, source, cid, title, cover, false, false, update, + finish, null, null, null, last, page, null, null)); + } + break; + case SUFFIX_CFBF: + case SUFFIX_CTBF: + for (int i = 0; i != array.length(); ++i) { + JSONObject object = array.getJSONObject(i); + int source = object.getInt(JSON_KEY_COMIC_SOURCE); + String cid = object.getString(JSON_KEY_COMIC_CID); + String title = object.getString(JSON_KEY_COMIC_TITLE); + String cover = object.getString(JSON_KEY_COMIC_COVER); + String update = object.optString(JSON_KEY_COMIC_UPDATE, null); + Boolean finish = object.has(JSON_KEY_COMIC_FINISH) ? + object.getBoolean(JSON_KEY_COMIC_FINISH) : null; + String last = object.optString(JSON_KEY_COMIC_LAST, null); + Integer page = object.has(JSON_KEY_COMIC_PAGE) ? + object.getInt(JSON_KEY_COMIC_PAGE) : null; + String chapter = object.optString(JSON_KEY_COMIC_CHAPTER, null); + Long favorite = object.has(JSON_KEY_COMIC_FAVORITE) ? + object.getLong(JSON_KEY_COMIC_FAVORITE) : null; + Long history = object.has(JSON_KEY_COMIC_HISTORY) ? + object.getLong(JSON_KEY_COMIC_HISTORY) : null; + if (favorite == null && history == null) { + // 以前只备份收藏 没有保存 favorite history + favorite = System.currentTimeMillis(); + } + list.add(new Comic(null, source, cid, title, cover, false, false, update, + finish, favorite, history, null, last, page, chapter, null)); + } + break; + } + return list; + } +} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/CCTuku.java b/app/src/main/java/com/hiroshi/cimoc/core/CCTuku.java deleted file mode 100644 index 0542443d..00000000 --- a/app/src/main/java/com/hiroshi/cimoc/core/CCTuku.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.hiroshi.cimoc.core; - -import com.hiroshi.cimoc.core.base.Manga; -import com.hiroshi.cimoc.model.Chapter; -import com.hiroshi.cimoc.model.Comic; -import com.hiroshi.cimoc.utils.Decryption; -import com.hiroshi.cimoc.utils.MachiSoup; - -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; - -import okhttp3.Request; - -/** - * Created by Hiroshi on 2016/7/28. - */ -public class CCTuku extends Manga { - - public CCTuku() { - super(Kami.SOURCE_CCTUKU, "http://m.tuku.cc"); - } - - @Override - protected Request buildSearchRequest(String keyword, int page) { - String url = host + "/comic/search?word=" + keyword + "&page=" + page; - return new Request.Builder().url(url).build(); - } - - @Override - protected List parseSearch(String html) { - MachiSoup.Node body = MachiSoup.body(html); - List list = new LinkedList<>(); - for (MachiSoup.Node node : body.list(".main-list > div > div > div")) { - String cid = node.attr("div:eq(1) > div:eq(0) > a", "href", "/", 2); - String title = node.text("div:eq(1) > div:eq(0) > a"); - String cover = node.attr("div:eq(0) > a > img", "src"); - String update = node.text("div:eq(1) > div:eq(1) > dl:eq(3) > dd > font"); - String author = node.text("div:eq(1) > div:eq(1) > dl:eq(1) > dd > a"); - list.add(new Comic(source, cid, title, cover, update, author, null)); - } - return list; - } - - @Override - protected Request buildIntoRequest(String cid) { - String url = host + "/comic/" + cid; - return new Request.Builder().url(url).build(); - } - - @Override - protected List parseInto(String html, Comic comic) { - List list = new LinkedList<>(); - MachiSoup.Node body = MachiSoup.body(html); - for (MachiSoup.Node node : body.list("ul.list-body > li > a")) { - String c_title = node.text(); - String c_path = node.attr("href", "/", 3); - list.add(new Chapter(c_title, c_path)); - } - - String title = body.text("div.title-banner > div.book-title > h1", 0, -2); - MachiSoup.Node detail = body.select("div.book > div > div:eq(0)"); - String cover = detail.attr("div:eq(0) > a > img", "src"); - String update = detail.text("div:eq(0) > dl:eq(5) > dd > font", 0, 10); - String author = detail.text("div:eq(0) > dl:eq(1) > dd > a"); - String intro = body.text("div.book-details > p:eq(1)"); - boolean status = "完结".equals(detail.text("div:eq(0) > div")); - comic.setInfo(title, cover, update, intro, author, status); - - return list; - } - - @Override - protected Request buildBrowseRequest(String cid, String path) { - String url = host + "/comic/" + cid + "/" + path; - return new Request.Builder().url(url).build(); - } - - @Override - protected String[] parseBrowse(String html) { - String packed = MachiSoup.match("eval(.*?)\\n;", html, 1); - if (packed != null) { - try { - String result = Decryption.evalDecrypt(packed); - String[] array = MachiSoup.match("pic_url='(.*?)';.*?tpf=(\\d+?);.*pages=(\\d+?);.*?pid=(.*?);.*?pic_extname='(.*?)';", result, 1, 2, 3, 4, 5); - if (array != null) { - int tpf = Integer.parseInt(array[1]) + 1; - int pages = Integer.parseInt(array[2]); - String format = "http://tkpic.um5.cc/" + array[3] + "/" + array[0] + "/%0" + tpf + "d." + array[4]; - String[] images = new String[pages]; - for (int i = 0; i != pages; ++i) { - images[i] = String.format(Locale.CHINA, format, i + 1); - } - return images; - } - } catch (Exception e) { - e.printStackTrace(); - } - } - return null; - } - -} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/ComicManager.java b/app/src/main/java/com/hiroshi/cimoc/core/ComicManager.java deleted file mode 100644 index 02b79e13..00000000 --- a/app/src/main/java/com/hiroshi/cimoc/core/ComicManager.java +++ /dev/null @@ -1,232 +0,0 @@ -package com.hiroshi.cimoc.core; - -import android.database.Cursor; - -import com.hiroshi.cimoc.CimocApplication; -import com.hiroshi.cimoc.model.Chapter; -import com.hiroshi.cimoc.model.Comic; -import com.hiroshi.cimoc.model.ComicDao; -import com.hiroshi.cimoc.model.ComicDao.Properties; -import com.hiroshi.cimoc.model.EventMessage; -import com.hiroshi.cimoc.model.MiniComic; -import com.hiroshi.cimoc.utils.ExLog; - -import org.greenrobot.eventbus.EventBus; - -import java.util.LinkedList; -import java.util.List; - -/** - * Created by Hiroshi on 2016/7/9. - */ -public class ComicManager { - - private static ComicManager mComicManager; - - private ComicDao mComicDao; - - private ComicManager() { - mComicDao = CimocApplication.getDaoSession().getComicDao(); - } - - public void restoreFavorite(final List list) { - mComicDao.getSession().runInTx(new Runnable() { - @Override - public void run() { - List result = new LinkedList<>(); - for (Comic comic : list) { - ExLog.d("ComicManager", "get " + comic.getTitle()); - Comic temp = mComicDao.queryBuilder() - .where(Properties.Source.eq(comic.getSource()), Properties.Cid.eq(comic.getCid())) - .unique(); - if (temp == null) { - ExLog.d("ComicManager", "insert " + comic.getTitle()); - comic.setFavorite(System.currentTimeMillis()); - long id = mComicDao.insert(comic); - comic.setId(id); - result.add(0, new MiniComic(comic)); - } else if (temp.getFavorite() == null) { - ExLog.d("ComicManager", "update " + comic.getTitle()); - temp.setFavorite(System.currentTimeMillis()); - mComicDao.update(temp); - result.add(0, new MiniComic(temp)); - } - } - EventBus.getDefault().post(new EventMessage(EventMessage.RESTORE_FAVORITE, result)); - } - }); - } - - public void cleanHistory() { - mComicDao.getSession().runInTx(new Runnable() { - @Override - public void run() { - List list = mComicDao.queryBuilder().where(Properties.History.isNotNull()).list(); - for (Comic comic : list) { - if (comic.getFavorite() != null) { - comic.setHistory(null); - mComicDao.update(comic); - } else { - mComicDao.delete(comic); - } - } - EventBus.getDefault().post(new EventMessage(EventMessage.DELETE_HISTORY, list.size())); - } - }); - } - - public List listBackup() { - return mComicDao.queryBuilder().where(Properties.Favorite.isNotNull()).list(); - } - - public List listFavorite() { - Cursor cursor = mComicDao.queryBuilder() - .where(ComicDao.Properties.Favorite.isNotNull()) - .orderDesc(Properties.Favorite) - .buildCursor() - .query(); - return listByCursor(cursor); - } - - public List listHistory() { - Cursor cursor = mComicDao.queryBuilder() - .where(Properties.History.isNotNull()) - .orderDesc(Properties.History) - .buildCursor() - .query(); - return listByCursor(cursor); - } - - private List listByCursor(Cursor cursor) { - List list = new LinkedList<>(); - while (cursor.moveToNext()) { - long id = cursor.getLong(0); - int source = cursor.getInt(1); - String cid = cursor.getString(2); - String title = cursor.getString(3); - String cover = cursor.getString(4); - String update = cursor.getString(5); - list.add(new MiniComic(id, source, cid, title, cover, update)); - } - cursor.close(); - return list; - } - - private Comic comic; - private List chapters; - private boolean isHistory; - - public void setComic(Long id, int source, String cid) { - isHistory = false; - if (id == null) { - comic = mComicDao.queryBuilder() - .where(Properties.Source.eq(source), Properties.Cid.eq(cid)) - .unique(); - } else { - comic = mComicDao.load(id); - } - if (comic == null) { - comic = new Comic(source, cid); - } - ExLog.d("ComicManager", "init id = " + comic.getId() + " source = " + comic.getSource() + " cid = " + comic.getCid()); - } - - public void setChapters(List chapters) { - this.chapters = chapters; - ExLog.d("ComicManager", "set chapter list and the number is " + chapters.size()); - } - - public void setLast(String path) { - comic.setLast(path); - comic.setPage(1); - comic.setHistory(System.currentTimeMillis()); - if (!isComicExist()) { - long id = mComicDao.insert(comic); - comic.setId(id); - ExLog.d("ComicManager", "insert " + comic.getTitle() + " to " + comic.getId()); - } - if (!isHistory) { - EventBus.getDefault().post(new EventMessage(EventMessage.HISTORY_COMIC, new MiniComic(comic))); - isHistory = true; - } - ExLog.d("ComicManager", "set the last path of" + " [" + comic.getId() + "] " + comic.getTitle() + " to " + path); - } - - public void afterRead(int page) { - comic.setPage(page); - ExLog.d("ComicManager", "set the last page of" + " [" + comic.getId() + "] " + comic.getTitle() + " to " + page); - EventBus.getDefault().post(new EventMessage(EventMessage.AFTER_READ, comic.getLast())); - } - - public void favoriteComic() { - comic.setFavorite(System.currentTimeMillis()); - if (!isComicExist()) { - long id = mComicDao.insert(comic); - comic.setId(id); - ExLog.d("ComicManager", "insert " + comic.getTitle() + " to " + comic.getId()); - } - ExLog.d("ComicManager", "favorite" + " [" + comic.getId() + "] " + comic.getTitle()); - EventBus.getDefault().post(new EventMessage(EventMessage.FAVORITE_COMIC, new MiniComic(comic))); - } - - public void unfavoriteComic() { - long id = comic.getId(); - comic.setFavorite(null); - if (!isComicHistory()) { - mComicDao.delete(comic); - comic.setId(null); - ExLog.d("ComicManager", "delete" + " [" + id + "] " + comic.getTitle()); - } - ExLog.d("ComicManager", "unfavorite" + " [" + id + "] " + comic.getTitle()); - EventBus.getDefault().post(new EventMessage(EventMessage.UN_FAVORITE_COMIC, id)); - } - - public List getChapters() { - return chapters; - } - - public Comic getComic() { - return comic; - } - - public boolean isComicStar() { - return comic.getFavorite() != null; - } - - public boolean isComicHistory() { - return comic.getHistory() != null; - } - - public boolean isComicExist() { - return comic.getId() != null; - } - - public int getSource() { - return comic.getSource(); - } - - public String getCid() { - return comic.getCid(); - } - - public String getLast() { - return comic.getLast(); - } - - public void saveComic() { - if (isComicExist()) { - ExLog.d("ComicManager", "save" + " [" + comic.getId() + "] " + comic.getTitle()); - mComicDao.update(comic); - } - comic = null; - ExLog.d("ComicManager", "clear comic"); - } - - public static ComicManager getInstance() { - if (mComicManager == null) { - mComicManager = new ComicManager(); - } - return mComicManager; - } - -} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/Dmzj.java b/app/src/main/java/com/hiroshi/cimoc/core/Dmzj.java deleted file mode 100644 index d2e236da..00000000 --- a/app/src/main/java/com/hiroshi/cimoc/core/Dmzj.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.hiroshi.cimoc.core; - -import com.hiroshi.cimoc.core.base.Manga; -import com.hiroshi.cimoc.model.Chapter; -import com.hiroshi.cimoc.model.Comic; -import com.hiroshi.cimoc.utils.MachiSoup; -import com.hiroshi.cimoc.utils.MachiSoup.Node; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; - -import okhttp3.Request; - -/** - * Created by Hiroshi on 2016/7/8. - */ -public class Dmzj extends Manga { - - public Dmzj() { - super(Kami.SOURCE_DMZJ, "http://m.dmzj.com"); - } - - @Override - protected Request buildSearchRequest(String keyword, int page) { - if (page == 1) { - String url = "http://s.acg.178.com/comicsum/search.php?s=" + keyword; - return new Request.Builder().url(url).build(); - } - return null; - } - - @Override - protected List parseSearch(String html) { - String jsonString = MachiSoup.match("g_search_data = (.*);", html, 1); - List list = new LinkedList<>(); - if (jsonString != null) { - try { - JSONArray array = new JSONArray(jsonString); - for (int i = 0; i != array.length(); ++i) { - JSONObject object = array.getJSONObject(i); - if (object.getInt("hidden") == 1) { - continue; - } - String cid = object.getString("id"); - String title = object.getString("name"); - String cover = object.getString("cover"); - long time = object.getLong("last_updatetime") * 1000; - String update = new SimpleDateFormat("yyyy-MM-dd").format(new Date(time)); - String author = object.getString("authors"); - boolean status = object.getInt("status_tag_id") == 2310; - list.add(new Comic(source, cid, title, cover, update, author, status)); - } - return list; - } catch (Exception e) { - e.printStackTrace(); - } - } - return null; - } - - @Override - protected Request buildIntoRequest(String cid) { - String url = host + "/info/" + cid + ".html"; - return new Request.Builder().url(url).build(); - } - - @Override - protected List parseInto(String html, Comic comic) { - String jsonString = MachiSoup.match("\"data\":(\\[.*?\\])", html, 1); - List list = new LinkedList<>(); - if (jsonString != null) { - try { - JSONArray array = new JSONArray(jsonString); - for (int i = 0; i != array.length(); ++i) { - JSONObject object = array.getJSONObject(i); - String c_title = object.getString("chapter_name"); - String c_path = object.getString("id"); - list.add(new Chapter(c_title, c_path)); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - Node doc = MachiSoup.body(html); - String intro = doc.text(".txtDesc", 3); - Node detail = doc.select(".Introduct_Sub"); - String title = detail.attr("#Cover > img", "title"); - String cover = detail.attr("#Cover > img", "src"); - String author = detail.text(".sub_r > p:eq(0) > a"); - boolean status = "已完结".equals(detail.text(".sub_r > p:eq(2) > a:eq(3)")); - String update = detail.text(".sub_r > p:eq(3) > .date", " ", 0); - comic.setInfo(title, cover, update, intro, author, status); - - return list; - } - - @Override - protected Request buildBrowseRequest(String cid, String path) { - String url = host + "/view/" + cid + "/" + path + ".html"; - return new Request.Builder().url(url).build(); - } - - @Override - protected String[] parseBrowse(String html) { - String jsonString = MachiSoup.match("\"page_url\":(\\[.*?\\])", html, 1); - if (jsonString != null) { - try { - JSONArray array = new JSONArray(jsonString); - String[] images = new String[array.length()]; - for (int i = 0; i != images.length; ++i) { - images[i] = array.getString(i); - } - return images; - } catch (Exception e) { - e.printStackTrace(); - } - } - return null; - } - -} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/Download.java b/app/src/main/java/com/hiroshi/cimoc/core/Download.java new file mode 100644 index 00000000..eb9ebd16 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/core/Download.java @@ -0,0 +1,374 @@ +package com.hiroshi.cimoc.core; + +import android.content.ContentResolver; +import android.util.Pair; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Task; +import com.hiroshi.cimoc.saf.DocumentFile; +import com.hiroshi.cimoc.utils.DecryptionUtils; +import com.hiroshi.cimoc.utils.DocumentUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +import rx.Observable; +import rx.Subscriber; +import rx.schedulers.Schedulers; + +/** + * Created by Hiroshi on 2016/9/9. + */ +public class Download { + + /** + * version 1 [1.4.3.0, ...) + * comic: + * { + * list: 章节列表 array + * [{ + * title: 章节名称 string + * path: 章节路径 string + * }] + * source: 图源 int + * cid: 漫画ID string + * title: 标题 string + * cover: 封面 string + * type: 类型 string ("comic") + * version: 版本 string ("1") + * } + * chapter: + * { + * title: 章节名称 string + * path: 章节路径 string + * type: 类型 string ("chapter") + * version: 版本 string ("1") + * } + *

+ * version 2 [遥遥无期, 遥遥无期) + * comic: + * { + * list: 章节列表 array + * [ 章节路径 string ] + * source: 图源 int + * cid: 漫画ID string + * title: 标题 string + * cover: 封面 string + * type: 类型 int (1) + * version: 版本 int (2) + * } + * chapter: + * { + * title: 章节名称 string + * path: 章节路径 string + * max: 总页数 int + * list: 图片列表 array + * [ 文件名 string ] + * type: 类型 int (2) + * version: 版本 int (2) + * } + */ + + private static final String JSON_KEY_VERSION = "version"; + private static final String JSON_KEY_TYPE = "type"; + private static final String JSON_KEY_TYPE_COMIC = "comic"; + private static final String JSON_KEY_TYPE_CHAPTER = "chapter"; + private static final String JSON_KEY_COMIC_LIST = "list"; + private static final String JSON_KEY_COMIC_SOURCE = "source"; + private static final String JSON_KEY_COMIC_CID = "cid"; + private static final String JSON_KEY_COMIC_TITLE = "title"; + private static final String JSON_KEY_COMIC_COVER = "cover"; + private static final String JSON_KEY_CHAPTER_PATH = "path"; + private static final String JSON_KEY_CHAPTER_TITLE = "title"; + + private static final String DOWNLOAD = "download"; + private static final String FILE_INDEX = "index.cdif"; + private static final String NO_MEDIA = ".nomedia"; + + private static void createNoMedia(DocumentFile root) { + DocumentFile home = DocumentUtils.getOrCreateSubDirectory(root, DOWNLOAD); + DocumentUtils.getOrCreateFile(home, NO_MEDIA); + } + + private static DocumentFile createComicIndex(DocumentFile root, Comic comic) { + DocumentFile home = DocumentUtils.getOrCreateSubDirectory(root, DOWNLOAD); + DocumentFile source = DocumentUtils.getOrCreateSubDirectory(home, String.valueOf(comic.getSource())); + DocumentFile dir = DocumentUtils.getOrCreateSubDirectory(source, comic.getCid()); + if (dir != null) { + return DocumentUtils.getOrCreateFile(dir, FILE_INDEX); + } + return null; + } + + /** + * 写漫画索引,不关心是否成功,若没有索引文件,则不能排序章节及扫描恢复漫画,但不影响下载及观看 + * + * @param list + * @param comic + */ + public static void updateComicIndex(ContentResolver resolver, DocumentFile root, List list, Comic comic) { + try { + createNoMedia(root); + String jsonString = getJsonFromComic(list, comic); + DocumentFile file = createComicIndex(root, comic); + DocumentUtils.writeStringToFile(resolver, file, "cimoc".concat(jsonString)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static String getJsonFromComic(List list, Comic comic) throws JSONException { + JSONObject object = new JSONObject(); + object.put(JSON_KEY_VERSION, "1"); + object.put(JSON_KEY_TYPE, JSON_KEY_TYPE_COMIC); + object.put(JSON_KEY_COMIC_SOURCE, comic.getSource()); + object.put(JSON_KEY_COMIC_CID, comic.getCid()); + object.put(JSON_KEY_COMIC_TITLE, comic.getTitle()); + object.put(JSON_KEY_COMIC_COVER, comic.getCover()); + JSONArray array = new JSONArray(); + for (Chapter chapter : list) { + JSONObject temp = new JSONObject(); + temp.put(JSON_KEY_CHAPTER_TITLE, chapter.getTitle()); + temp.put(JSON_KEY_CHAPTER_PATH, chapter.getPath()); + array.put(temp); + } + object.put(JSON_KEY_COMIC_LIST, array); + return object.toString(); + } + + public static DocumentFile updateChapterIndex(ContentResolver resolver, DocumentFile root, Task task) { + try { + String jsonString = getJsonFromChapter(task.getTitle(), task.getPath()); + DocumentFile dir1 = DocumentUtils.getOrCreateSubDirectory(root, DOWNLOAD); + DocumentFile dir2 = DocumentUtils.getOrCreateSubDirectory(dir1, String.valueOf(task.getSource())); + DocumentFile dir3 = DocumentUtils.getOrCreateSubDirectory(dir2, task.getCid()); + DocumentFile dir4 = DocumentUtils.getOrCreateSubDirectory(dir3, DecryptionUtils.urlDecrypt(task.getPath().replaceAll("/|\\?", "-"))); + if (dir4 != null) { + DocumentFile file = DocumentUtils.getOrCreateFile(dir4, FILE_INDEX); + DocumentUtils.writeStringToFile(resolver, file, "cimoc".concat(jsonString)); + return dir4; + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private static String getJsonFromChapter(String title, String path) throws JSONException { + JSONObject object = new JSONObject(); + object.put(JSON_KEY_VERSION, "1"); + object.put(JSON_KEY_TYPE, JSON_KEY_TYPE_CHAPTER); + object.put(JSON_KEY_CHAPTER_TITLE, title); + object.put(JSON_KEY_CHAPTER_PATH, path); + return object.toString(); + } + + /** + * 1.4.4 以前位于 存储目录/download/图源名称/漫画名称 + * 1.4.4 以后位于 存储目录/download/图源ID/漫画ID + * + * @param root 存储目录 + * @param comic + * @return + */ + private static DocumentFile getComicDir(DocumentFile root, Comic comic, String title) { + DocumentFile result = DocumentUtils.findFile(root, DOWNLOAD, String.valueOf(comic.getSource()), comic.getCid()); + if (result == null) { + result = DocumentUtils.findFile(root, DOWNLOAD, title, comic.getTitle()); + } + return result; + } + + public static List getComicIndex(ContentResolver resolver, DocumentFile root, Comic comic, String title) { + DocumentFile dir = getComicDir(root, comic, title); + if (dir != null) { + DocumentFile file = dir.findFile(FILE_INDEX); + if (file != null) { + if (hasMagicNumber(resolver, file)) { + String jsonString = DocumentUtils.readLineFromFile(resolver, file); + if (jsonString != null) { + try { + return readPathFromJson(jsonString.substring(5)); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + } + return null; + } + + private static List readPathFromJson(String jsonString) throws JSONException { + JSONObject jsonObject = new JSONObject(jsonString); + // We use "c" as the key in old version + JSONArray array = jsonObject.has(JSON_KEY_COMIC_LIST) ? jsonObject.getJSONArray(JSON_KEY_COMIC_LIST) : jsonObject.getJSONArray("c"); + int size = array.length(); + List list = new ArrayList<>(size); + for (int i = 0; i != size; ++i) { + JSONObject object = array.getJSONObject(i); + list.add(object.has(JSON_KEY_CHAPTER_PATH) ? object.getString(JSON_KEY_CHAPTER_PATH) : object.getString("p")); + } + return list; + } + + public static DocumentFile getChapterDir(DocumentFile root, Comic comic, Chapter chapter, String title) { + DocumentFile result = DocumentUtils.findFile(root, DOWNLOAD, String.valueOf(comic.getSource()), + comic.getCid(), DecryptionUtils.urlDecrypt(chapter.getPath().replaceAll("/|\\?", "-"))); + if (result == null) { + result = DocumentUtils.findFile(root, DOWNLOAD, title, comic.getTitle(), chapter.getTitle()); + } + return result; + } + + public static Observable> images(final DocumentFile root, final Comic comic, final Chapter chapter, final String title) { + return Observable.create(new Observable.OnSubscribe>() { + @Override + public void call(Subscriber> subscriber) { + DocumentFile dir = getChapterDir(root, comic, chapter, title); + List files = dir.listFiles(new DocumentFile.DocumentFileFilter() { + @Override + public boolean call(DocumentFile file) { + return !file.getName().endsWith("cdif"); + } + }, new Comparator() { + @Override + public int compare(DocumentFile lhs, DocumentFile rhs) { + return lhs.getName().compareTo(rhs.getName()); + } + }); + + List list = Storage.buildImageUrlFromDocumentFile(files, chapter.getPath(), chapter.getCount()); + if (list.size() != 0) { + subscriber.onNext(list); + subscriber.onCompleted(); + } else { + subscriber.onError(new Exception()); + } + } + }).subscribeOn(Schedulers.io()); + } + + public static void delete(DocumentFile root, Comic comic, List list, String title) { + for (Chapter chapter : list) { + DocumentFile dir = getChapterDir(root, comic, chapter, title); + if (dir != null) { + dir.delete(); + } + } + } + + public static void delete(DocumentFile root, Comic comic, String title) { + DocumentFile dir = getComicDir(root, comic, title); + if (dir != null) { + dir.delete(); + } + } + + private static String getIndexJsonFromDir(ContentResolver resolver, DocumentFile dir) { + if (dir.isDirectory()) { + DocumentFile file = dir.findFile(FILE_INDEX); + if (hasMagicNumber(resolver, file)) { + String jsonString = DocumentUtils.readLineFromFile(resolver, file); + if (jsonString != null) { + return jsonString.substring(5); + } + } + } + return null; + } + + private static Task buildTaskFromDir(ContentResolver resolver, DocumentFile dir) { + String jsonString = getIndexJsonFromDir(resolver, dir); + if (jsonString != null) { + try { + JSONObject jsonObject = new JSONObject(jsonString); + if (JSON_KEY_TYPE_CHAPTER.equals(jsonObject.get(JSON_KEY_TYPE))) { + int count = DocumentUtils.countWithoutSuffix(dir, "cdif"); + if (count != 0) { + String path = jsonObject.getString(JSON_KEY_CHAPTER_PATH); + String title = jsonObject.getString(JSON_KEY_CHAPTER_TITLE); + return new Task(null, -1, path, title, count, count); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return null; + } + + /** + * 1.4.3 之后,因为有在章节文件夹内写索引文件,所以恢复起来简单 + * 1.4.3 之前,章节文件夹内没有索引文件,需要比较文件夹名称,有点麻烦,暂不实现 + */ + private static Comic buildComicFromDir(ContentResolver resolver, DocumentFile dir) { + String jsonString = getIndexJsonFromDir(resolver, dir); + if (jsonString != null) { + try { + JSONObject jsonObject = new JSONObject(jsonString); + if (!JSON_KEY_TYPE_COMIC.equals(jsonObject.get(JSON_KEY_TYPE))) { + return null; + } + int source = jsonObject.getInt(JSON_KEY_COMIC_SOURCE); + String title = jsonObject.getString(JSON_KEY_COMIC_TITLE); + String cid = jsonObject.getString(JSON_KEY_COMIC_CID); + String cover = jsonObject.getString(JSON_KEY_COMIC_COVER); + return new Comic(source, cid, title, cover, System.currentTimeMillis()); + } catch (Exception e) { + e.printStackTrace(); + } + } + return null; + } + + public static Observable>> scan(final ContentResolver resolver, final DocumentFile root) { + return Observable.create(new Observable.OnSubscribe>>() { + @Override + public void call(Subscriber>> subscriber) { + root.refresh(); + DocumentFile downloadDir = DocumentUtils.getOrCreateSubDirectory(root, DOWNLOAD); + if (downloadDir != null) { + for (DocumentFile sourceDir : downloadDir.listFiles()) { + if (sourceDir.isDirectory()) { + for (DocumentFile comicDir : sourceDir.listFiles()) { + Comic comic = buildComicFromDir(resolver, comicDir); + if (comic != null) { + List list = new LinkedList<>(); + for (DocumentFile chapterDir : comicDir.listFiles()) { + Task task = buildTaskFromDir(resolver, chapterDir); + if (task != null) { + list.add(task); + } + } + if (!list.isEmpty()) { + subscriber.onNext(Pair.create(comic, list)); + } + } + } + } + } + } + subscriber.onCompleted(); + } + }).subscribeOn(Schedulers.io()); + } + + private static boolean hasMagicNumber(ContentResolver resolver, DocumentFile file) { + if (file != null) { + char[] magic = DocumentUtils.readCharFromFile(resolver, file, 5); + return Arrays.equals(magic, "cimoc".toCharArray()); + } + return false; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/EHentai.java b/app/src/main/java/com/hiroshi/cimoc/core/EHentai.java deleted file mode 100644 index a05e5113..00000000 --- a/app/src/main/java/com/hiroshi/cimoc/core/EHentai.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.hiroshi.cimoc.core; - -import com.hiroshi.cimoc.core.base.Manga; -import com.hiroshi.cimoc.model.Chapter; -import com.hiroshi.cimoc.model.Comic; -import com.hiroshi.cimoc.utils.MachiSoup; -import com.hiroshi.cimoc.utils.MachiSoup.Node; - -import java.util.LinkedList; -import java.util.List; - -import okhttp3.Request; - -/** - * Created by Hiroshi on 2016/7/26. - */ -public class EHentai extends Manga { - - public EHentai() { - super(Kami.SOURCE_EHENTAI, "http://lofi.e-hentai.org"); - } - - @Override - protected Request buildSearchRequest(String keyword, int page) { - String url = host + "?f_search=" + keyword + "&page=" + (page - 1); - return new Request.Builder().url(url).build(); - } - - @Override - protected List parseSearch(String html) { - Node body = MachiSoup.body(html); - List nodes = body.list("#ig > div > table > tbody > tr"); - List list = new LinkedList<>(); - for (Node node : nodes) { - String cid = node.attr("td:eq(1) > table > tbody > tr:eq(0) > td > a", "href"); - cid = cid.substring(host.length() + 3, cid.length() - 1); - String title = node.text("td:eq(1) > table > tbody > tr:eq(0) > td > a"); - String cover = node.attr("td:eq(0) > a > img", "src"); - String update = node.text("td:eq(1) > table > tbody > tr:eq(1) > td:eq(1)", 0, 10); - String author = node.text("td:eq(1) > table > tbody > tr:eq(1) > td:eq(1)", 20); - list.add(new Comic(source, cid, title, cover, update, author, true)); - } - return list; - } - - @Override - protected Request buildIntoRequest(String cid) { - String url = "http://g.e-hentai.org/g/" + cid; - return new Request.Builder().url(url).header("Cookie", "nw=1").build(); - } - - @Override - protected List parseInto(String html, Comic comic) { - List list = new LinkedList<>(); - Node doc = MachiSoup.body(html); - String length = doc.text("#gdd > table > tbody > tr:eq(5) > td:eq(1)", " ", 0); - int size = Integer.parseInt(length) % 8 == 0 ? Integer.parseInt(length) / 8 : Integer.parseInt(length) / 8 + 1; - for (int i = 0; i != size; ++i) { - list.add(0, new Chapter("Ch" + i, "/" + i)); - } - - String update = doc.text("#gdd > table > tbody > tr:eq(0) > td:eq(1)", 0, 10); - String title = doc.text("#gn"); - String intro = doc.text("#gj"); - String author = doc.text("#taglist > table > tbody > tr > td:eq(1) > div > a[id^=ta_artist]"); - String cover = doc.attr("#gd1 > img", "src"); - comic.setInfo(title, cover, update, intro, author, true); - - return list; - } - - @Override - protected Request buildBrowseRequest(String cid, String path) { - String url = host + "/g/" + cid + path; - return new Request.Builder().url(url).build(); - } - - @Override - protected String[] parseBrowse(String html) { - Node body = MachiSoup.body(html); - List list = body.list("#gh > div > a"); - String[] array = new String[list.size()]; - for (int i = 0; i != list.size(); ++i) { - String url = list.get(i).attr("href"); - String result = execute(url); - if (result != null) { - Node node = MachiSoup.body(result); - array[i] = node.attr("#sm", "src"); - } else { - array[i] = null; - } - } - return array; - } - -} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/HHAAZZ.java b/app/src/main/java/com/hiroshi/cimoc/core/HHAAZZ.java deleted file mode 100644 index 99774632..00000000 --- a/app/src/main/java/com/hiroshi/cimoc/core/HHAAZZ.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.hiroshi.cimoc.core; - -import com.hiroshi.cimoc.core.base.Manga; -import com.hiroshi.cimoc.model.Chapter; -import com.hiroshi.cimoc.model.Comic; -import com.hiroshi.cimoc.utils.MachiSoup; -import com.hiroshi.cimoc.utils.MachiSoup.Node; - -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; - -import okhttp3.Request; - -/** - * Created by Hiroshi on 2016/7/26. - */ -public class HHAAZZ extends Manga { - - public HHAAZZ() { - super(Kami.SOURCE_HHAAZZ, "http://hhaazz.com"); - } - - @Override - protected Request buildSearchRequest(String keyword, int page) { - if (page == 1) { - String url = host + "/comicsearch/s.aspx?s=" + keyword; - return new Request.Builder().url(url).build(); - } - return null; - } - - @Override - protected List parseSearch(String html) { - Node body = MachiSoup.body(html); - List nodes = body.list(".se-list > li"); - List list = new LinkedList<>(); - for (Node node : nodes) { - String cid = node.attr("a:eq(0)", "href", "/", 4); - String title = node.text("a:eq(0) > div > h3"); - String cover = node.attr("a:eq(0) > img", "src"); - String update = node.text("a:eq(0) > div > p:eq(4) > span", 0, 10); - String author = node.text("a:eq(0) > div > p:eq(1)"); - boolean status = "完结".equals(node.text("a:eq(1) > span:eq(1)")); - list.add(new Comic(source, cid, title, cover, update, author, status)); - } - return list; - } - - @Override - protected Request buildIntoRequest(String cid) { - String url = host + "/comic/" + cid; - return new Request.Builder().url(url).build(); - } - - @Override - protected List parseInto(String html, Comic comic) { - List list = new LinkedList<>(); - Node doc = MachiSoup.body(html); - List nodes = doc.list("#sort_div_p > a"); - for (Node node : nodes) { - String c_title = node.attr("title"); - String c_path = node.attr("href").substring(host.length()); - list.add(new Chapter(c_title, c_path)); - } - - Node detail = doc.select(".main > div > div.pic"); - String title = detail.text("div:eq(1) > h3"); - String cover = detail.attr("img:eq(0)", "src"); - String update = detail.text("div:eq(1) > p:eq(5)", 5); - String author = detail.text("div:eq(1) > p:eq(1)", 3); - String intro = doc.text("#detail_block > div > p"); - boolean status = detail.text("div:eq(1) > p:eq(4)").contains("完结"); - comic.setInfo(title, cover, update, intro, author, status); - - return list; - } - - @Override - protected Request buildBrowseRequest(String cid, String path) { - String url = host + path; - return new Request.Builder().url(url).build(); - } - - @Override - protected String[] parseBrowse(String html) { - String[] str = MachiSoup.match("sFiles=\"(.*?)\";var sPath=\"(\\d+)\"", html, 1, 2); - if (str != null) { - String[] result = unsuan(str[0]); - String domain = String.format(Locale.CHINA, "http://x8.1112223333.com:9393/dm%02d", Integer.parseInt(str[1])); - String[] array = new String[result.length]; - for (int i = 0; i != result.length; ++i) { - array[i] = domain + result[i]; - } - return array; - } - return null; - } - - private static String[] unsuan(String str) { - int num = str.length() - str.charAt(str.length() - 1) + 'a'; - String code = str.substring(num - 13, num - 3); - String cut = str.substring(num - 3, num - 2); - str = str.substring(0, num - 13); - for (int i = 0; i < 10; ++i) { - str = str.replace(code.charAt(i), (char) ('0' + i)); - } - StringBuilder builder = new StringBuilder(); - String[] array = str.split(cut); - for (int i = 0; i != array.length; ++i) { - builder.append((char) Integer.parseInt(array[i])); - } - return builder.toString().split("\\|"); - } - -} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/IKanman.java b/app/src/main/java/com/hiroshi/cimoc/core/IKanman.java deleted file mode 100644 index e771d0d9..00000000 --- a/app/src/main/java/com/hiroshi/cimoc/core/IKanman.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.hiroshi.cimoc.core; - -import com.hiroshi.cimoc.core.base.Manga; -import com.hiroshi.cimoc.model.Chapter; -import com.hiroshi.cimoc.model.Comic; -import com.hiroshi.cimoc.utils.Decryption; -import com.hiroshi.cimoc.utils.MachiSoup; -import com.hiroshi.cimoc.utils.MachiSoup.Node; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.util.LinkedList; -import java.util.List; - -import okhttp3.Request; - -/** - * Created by Hiroshi on 2016/7/8. - */ -public class IKanman extends Manga { - - public IKanman() { - super(Kami.SOURCE_IKANMAN, "http://m.ikanman.com"); - } - - @Override - protected Request buildSearchRequest(String keyword, int page) { - String url = host + "/s/" + keyword + ".html?page=" + page; - return new Request.Builder().url(url).build(); - } - - @Override - protected List parseSearch(String html) { - Node body = MachiSoup.body(html); - List list = new LinkedList<>(); - for (Node node : body.list("#detail > li > a")) { - String cid = node.attr("href", "/", 2); - String title = node.text("h3"); - String cover = node.attr("div > img", "data-src"); - String update = node.text("dl:eq(5) > dd"); - String author = node.text("dl:eq(2) > dd"); - boolean status = "完结".equals(node.text("div > i")); - list.add(new Comic(source, cid, title, cover, update, author, status)); - } - return list; - } - - @Override - protected Request buildIntoRequest(String cid) { - String url = host + "/comic/" + cid; - return new Request.Builder().url(url).build(); - } - - @Override - protected List parseInto(String html, Comic comic) { - List list = new LinkedList<>(); - Node body = MachiSoup.body(html); - for (Node node : body.list("#chapterList > ul > li > a")) { - String c_title = node.text("b"); - String c_path = node.attr("href", "/|\\.", 3); - list.add(new Chapter(c_title, c_path)); - } - - String title = body.text("div.main-bar > h1"); - Node detail = body.select("div.book-detail"); - String cover = detail.attr("div:eq(0) > div:eq(0) > img", "src"); - String update = detail.text("div:eq(0) > dl:eq(2) > dd"); - String author = detail.attr("div:eq(0) > dl:eq(3) > dd > a", "title"); - Node temp = detail.id("bookIntro"); - String intro = temp.exist("p:eq(0)") ? temp.text() : temp.text("p:eq(0)"); - boolean status = "完结".equals(detail.text("div:eq(0) > div:eq(0) > i")); - comic.setInfo(title, cover, update, intro, author, status); - - return list; - } - - @Override - protected Request buildBrowseRequest(String cid, String path) { - String url = host + "/comic/" + cid + "/" + path + ".html"; - return new Request.Builder().url(url).build(); - } - - @Override - protected String[] parseBrowse(String html) { - String str = MachiSoup.match("decryptDES\\(\"(.*?)\"\\)", html, 1); - if (str != null) { - try { - String cipherStr = str.substring(8); - String keyStr = str.substring(0, 8); - String packed = Decryption.desDecrypt(keyStr, cipherStr); - String result = Decryption.evalDecrypt(packed.substring(4)); - - String jsonString = result.substring(11, result.length() - 9); - JSONObject info = new JSONObject(jsonString); - JSONArray array = info.getJSONArray("images"); - String[] images = new String[array.length()]; - for (int i = 0; i != array.length(); ++i) { - images[i] = "http://i.hamreus.com:8080" + array.getString(i); - } - return images; - } catch (Exception e) { - e.printStackTrace(); - } - } - return null; - } - -} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/Kami.java b/app/src/main/java/com/hiroshi/cimoc/core/Kami.java deleted file mode 100644 index b866c5c1..00000000 --- a/app/src/main/java/com/hiroshi/cimoc/core/Kami.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.hiroshi.cimoc.core; - -import com.hiroshi.cimoc.core.base.Manga; - -/** - * Created by Hiroshi on 2016/7/3. - */ -public class Kami { - - public static final int SOURCE_IKANMAN = 0; - public static final int SOURCE_DMZJ = 1; - public static final int SOURCE_HHAAZZ = 2; - public static final int SOURCE_CCTUKU = 3; - public static final int SOURCE_EHENTAI = 100; - - public static String getSourceById(int id) { - switch (id) { - default: - case SOURCE_IKANMAN: - return "看漫画"; - case SOURCE_DMZJ: - return "动漫之家"; - case SOURCE_HHAAZZ: - return "汗汗漫画"; - case SOURCE_CCTUKU: - return "CC图库"; - case SOURCE_EHENTAI: - return "E-Hentai"; - } - } - - public static String getRefererById(int id) { - switch (id) { - default: - case SOURCE_IKANMAN: - return "http://m.ikanman.com"; - case SOURCE_DMZJ: - return "http://m.dmzj.com/"; - case SOURCE_HHAAZZ: - return "http://hhaazz.com"; - case SOURCE_CCTUKU: - return "http://m.tuku.cc"; - case SOURCE_EHENTAI: - return "http://lofi.e-hentai.org"; - } - } - - public static Manga getMangaById(int id) { - switch (id) { - default: - case SOURCE_IKANMAN: - return new IKanman(); - case SOURCE_DMZJ: - return new Dmzj(); - case SOURCE_HHAAZZ: - return new HHAAZZ(); - case SOURCE_CCTUKU: - return new CCTuku(); - case SOURCE_EHENTAI: - return new EHentai(); - } - } - -} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/Local.java b/app/src/main/java/com/hiroshi/cimoc/core/Local.java new file mode 100644 index 00000000..648c1e8f --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/core/Local.java @@ -0,0 +1,188 @@ +package com.hiroshi.cimoc.core; + +import android.util.Pair; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Task; +import com.hiroshi.cimoc.saf.DocumentFile; +import com.hiroshi.cimoc.source.Locality; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import rx.Observable; +import rx.Subscriber; +import rx.schedulers.Schedulers; + +/** + * Created by Hiroshi on 2017/5/20. + */ + +public class Local { + + private static Pattern chapterPattern = null; + + public static Observable>>> scan(final DocumentFile root) { + return Observable.create(new Observable.OnSubscribe>>>() { + @Override + public void call(Subscriber>>> subscriber) { + List>> result = new ArrayList<>(); + + ScanInfo info = new ScanInfo(root); + countPicture(info); + if (info.count > 5) { + Pair> pair = Pair.create(buildComic(info.dir, info.cover), new ArrayList()); + pair.second.add(buildTask(info.dir, info.count, true)); + result.add(pair); + } else { + List list = new LinkedList<>(); + list.add(root); + + while (!list.isEmpty()) { + DocumentFile dir = list.get(0); + + List guessChapter = new LinkedList<>(); + List guessComic = new LinkedList<>(); + List guessOther = classify(guessChapter, guessComic, dir); + + if (guessChapter.size() > 2 * guessComic.size()) { // 章节 + result.add(merge(dir, guessChapter, guessComic)); + } else { // 单章节漫画 + split(guessChapter, result); + split(guessComic, result); + list.addAll(guessOther); + } + + list.remove(0); + } + } + subscriber.onNext(result); + subscriber.onCompleted(); + } + }).subscribeOn(Schedulers.io()); + } + + public static Observable> images(final DocumentFile dir, final Chapter chapter) { + return Observable.create(new Observable.OnSubscribe>() { + @Override + public void call(Subscriber> subscriber) { + List files = dir.listFiles(new DocumentFile.DocumentFileFilter() { + @Override + public boolean call(DocumentFile file) { + return file.isFile() && StringUtils.endWith(file.getName(), "jpg", "png", "jpeg"); + } + }, new Comparator() { + @Override + public int compare(DocumentFile lhs, DocumentFile rhs) { + return lhs.getName().compareTo(rhs.getName()); + } + }); + List list = Storage.buildImageUrlFromDocumentFile(files, chapter.getTitle(), chapter.getCount()); + + if (list.size() != 0) { + subscriber.onNext(list); + subscriber.onCompleted(); + } else { + subscriber.onError(new Exception()); + } + } + }).subscribeOn(Schedulers.io()); + } + + private static void countPicture(ScanInfo info) { + String name = null; + int other = 0; + for (DocumentFile file : info.dir.listFiles()) { + if (file.isFile() && StringUtils.endWith(file.getName(), "png", "jpg", "jpeg")) { + ++info.count; + } else { + ++other; + } + if (name == null || file.getName().compareTo(name) < 0) { + name = file.getName(); + info.cover = file.getUri().toString(); + } + if (other > 5) { + info.count = 0; + break; + } + } + } + + private static List classify(List chapter, + List comic, + DocumentFile dir) { + List other = new LinkedList<>(); + for (DocumentFile file : dir.listFiles()) { + if (file.isDirectory()) { + ScanInfo info = new ScanInfo(file); + countPicture(info); + if (info.count > 5) { + if (isNameChapter(file)) { + chapter.add(info); + } else { + comic.add(info); + } + } else { + other.add(file); + } + } + } + return other; + } + + private static boolean isNameChapter(DocumentFile file) { + if (chapterPattern == null) { + chapterPattern = Pattern.compile("^[^(\\[]{0,5}[0-9]+|[0-9]+.{0,5}$"); + } + Matcher matcher = chapterPattern.matcher(file.getName()); + return matcher.find() && ((float) matcher.group().length() / file.getName().length() > 0.8); + } + + private static Comic buildComic(DocumentFile dir, String cover) { + return new Comic(null, Locality.TYPE, dir.getUri().toString(), dir.getName(), cover, + false, true, null, null, null, null, null, null, null, null, null); + } + + private static Task buildTask(DocumentFile dir, int count, boolean single) { + return single ? new Task(null, -1, dir.getUri().toString(), "第01话", count, count) : + new Task(null, -1, dir.getUri().toString(), dir.getName(), count, count); + } + + private static Pair> merge(DocumentFile dir, List list1, List list2) { + Pair> pair = Pair.create(buildComic(dir, list1.get(0).cover), new ArrayList()); + for (ScanInfo info : list1) { + pair.second.add(buildTask(info.dir, info.count, false)); + } + for (ScanInfo info : list2) { + pair.second.add(buildTask(info.dir, info.count, false)); + } + return pair; + } + + private static void split(List list, List>> result) { + for (ScanInfo info : list) { + Pair> pair = Pair.create(buildComic(info.dir, info.cover), new ArrayList()); + pair.second.add(buildTask(info.dir, info.count, true)); + result.add(pair); + } + } + + private static class ScanInfo { + DocumentFile dir = null; + String cover = null; + int count = 0; + + ScanInfo(DocumentFile dir) { + this.dir = dir; + } + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/Manga.java b/app/src/main/java/com/hiroshi/cimoc/core/Manga.java new file mode 100644 index 00000000..02c2264d --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/core/Manga.java @@ -0,0 +1,331 @@ +package com.hiroshi.cimoc.core; + +import com.hiroshi.cimoc.App; +import com.hiroshi.cimoc.manager.SourceManager; +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.parser.Parser; +import com.hiroshi.cimoc.parser.SearchIterator; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.InterruptedIOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import rx.Observable; +import rx.Subscriber; +import rx.schedulers.Schedulers; + +/** + * Created by Hiroshi on 2016/8/20. + */ +public class Manga { + + private static boolean indexOfIgnoreCase(String str, String search) { + return str.toLowerCase().indexOf(search.toLowerCase()) != -1; + } + + public static Observable getSearchResult(final Parser parser, final String keyword, final int page, final boolean strictSearch) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + try { + Request request = parser.getSearchRequest(keyword, page); + Random random = new Random(); + String html = getResponseBody(App.getHttpClient(), request); + SearchIterator iterator = parser.getSearchIterator(html, page); + if (iterator == null || iterator.empty()) { + throw new Exception(); + } + while (iterator.hasNext()) { + Comic comic = iterator.next(); +// if (comic != null && (comic.getTitle().indexOf(keyword) != -1 || comic.getAuthor().indexOf(keyword) != -1)) { + if (comic != null + && (indexOfIgnoreCase(comic.getTitle(), keyword) + || indexOfIgnoreCase(comic.getAuthor(), keyword) + || (!strictSearch))) { + subscriber.onNext(comic); + Thread.sleep(random.nextInt(200)); + } + } + subscriber.onCompleted(); + } catch (Exception e) { + subscriber.onError(e); + } + } + }).subscribeOn(Schedulers.io()); + } + + public static Observable> getComicInfo(final Parser parser, final Comic comic) { + return Observable.create(new Observable.OnSubscribe>() { + @Override + public void call(Subscriber> subscriber) { + try { +// Mongo mongo = new Mongo(); + List list = new ArrayList<>(); + +// list.addAll(mongo.QueryComicBase(comic)); + if (list.isEmpty()) { + comic.setUrl(parser.getUrl(comic.getCid())); + Request request = parser.getInfoRequest(comic.getCid()); + String html = getResponseBody(App.getHttpClient(), request); + parser.parseInfo(html, comic); + request = parser.getChapterRequest(html, comic.getCid()); + if (request != null) { + html = getResponseBody(App.getHttpClient(), request); + } + list = parser.parseChapter(html); +// mongo.UpdateComicBase(comic, list); + } + if (!list.isEmpty()) { + subscriber.onNext(list); + subscriber.onCompleted(); + } else { + throw new ParseErrorException(); + } + } catch (Exception e) { + subscriber.onError(e); + } + } + }).subscribeOn(Schedulers.io()); + } + + public static Observable> getCategoryComic(final Parser parser, final String format, + final int page) { + return Observable.create(new Observable.OnSubscribe>() { + @Override + public void call(Subscriber> subscriber) { + try { + Request request = parser.getCategoryRequest(format, page); + String html = getResponseBody(App.getHttpClient(), request); + List list = parser.parseCategory(html, page); + if (!list.isEmpty()) { + subscriber.onNext(list); + subscriber.onCompleted(); + } else { + throw new Exception(); + } + } catch (Exception e) { + subscriber.onError(e); + } + } + }).subscribeOn(Schedulers.io()); + } + + public static Observable> getChapterImage(final Comic mComic, + final Parser parser, + final String cid, + final String path) { + return Observable.create(new Observable.OnSubscribe>() { + @Override + public void call(Subscriber> subscriber) { + String html; +// Mongo mongo = new Mongo(); + List list = new ArrayList<>(); + try { +// List listdoc = new ArrayList<>(); +// list.addAll(mongo.QueryComicChapter(mComic, path)); + if (list.isEmpty()) { + Request request = parser.getImagesRequest(cid, path); + html = getResponseBody(App.getHttpClient(), request); + list = parser.parseImages(html); +// if (!list.isEmpty()) { +// mongo.InsertComicChapter(mComic, path, list); +// } + } + + if (list.isEmpty()) { + throw new Exception(); + } else { + for (ImageUrl imageUrl : list) { + imageUrl.setChapter(path); + } + subscriber.onNext(list); + subscriber.onCompleted(); + } + } catch (Exception e) { + subscriber.onError(e); + } + } + }).subscribeOn(Schedulers.io()); + } + + public static List getImageUrls(Parser parser, int source, String cid, String path) throws InterruptedIOException { + List list = new ArrayList<>(); +// Mongo mongo = new Mongo(); + Response response = null; + try { +// list.addAll(mongo.QueryComicChapter(source, cid, path)); + if (!list.isEmpty()) { + return list; + } + Request request = parser.getImagesRequest(cid, path); + response = App.getHttpClient().newCall(request).execute(); + if (response.isSuccessful()) { + list.addAll(parser.parseImages(response.body().string())); +// mongo.InsertComicChapter(source, cid, path, list); + } else { + throw new NetworkErrorException(); + } + } catch (InterruptedIOException e) { + throw e; + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (response != null) { + response.close(); + } + } + return list; + } + + public static String getLazyUrl(Parser parser, String url) throws InterruptedIOException { + Response response = null; + try { + Request request = parser.getLazyRequest(url); + response = App.getHttpClient().newCall(request).execute(); + if (response.isSuccessful()) { + return parser.parseLazy(response.body().string(), url); + } else { + throw new NetworkErrorException(); + } + } catch (InterruptedIOException e) { + throw e; + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (response != null) { + response.close(); + } + } + return null; + } + + public static Observable loadLazyUrl(final Parser parser, final String url) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + Request request = parser.getLazyRequest(url); + String newUrl = null; + try { + newUrl = parser.parseLazy(getResponseBody(App.getHttpClient(), request), url); + } catch (Exception e) { + e.printStackTrace(); + } + subscriber.onNext(newUrl); + subscriber.onCompleted(); + } + }).subscribeOn(Schedulers.io()); + } + + public static Observable> loadAutoComplete(final String keyword) { + return Observable.create(new Observable.OnSubscribe>() { + @Override + public void call(Subscriber> subscriber) { +// RequestBody body = new FormBody.Builder() +// .add("key", keyword) +// .add("s", "1") +// .build(); +// Request request = new Request.Builder() +// .url("http://m.ikanman.com/support/word.ashx") +// .post(body) +// .build(); + Request request = new Request.Builder() + .url("http://m.ac.qq.com/search/smart?word=" + keyword) + .build(); + try { + String jsonString = getResponseBody(App.getHttpClient(), request); +// JSONArray array = new JSONArray(jsonString); + JSONObject jsonObject = new JSONObject(jsonString); + JSONArray array = jsonObject.getJSONArray("data"); + List list = new ArrayList<>(); + for (int i = 0; i != array.length(); ++i) { +// list.add(array.getJSONObject(i).getString("t")); + list.add(array.getJSONObject(i).getString("title")); + } + subscriber.onNext(list); + subscriber.onCompleted(); + } catch (Exception e) { + subscriber.onError(e); + } + } + }).subscribeOn(Schedulers.io()); + } + + public static Observable checkUpdate( + final SourceManager manager, final List list) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(1500, TimeUnit.MILLISECONDS) + .readTimeout(1500, TimeUnit.MILLISECONDS) + .build(); + for (Comic comic : list) { + Parser parser = manager.getParser(comic.getSource()); + Request request = parser.getCheckRequest(comic.getCid()); + try { + String update = parser.parseCheck(getResponseBody(client, request)); + if (comic.getUpdate() != null && update != null && !comic.getUpdate().equals(update)) { + comic.setFavorite(System.currentTimeMillis()); + comic.setUpdate(update); + comic.setHighlight(true); + subscriber.onNext(comic); + continue; + } + } catch (Exception e) { + } + subscriber.onNext(null); + } + subscriber.onCompleted(); + } + }).subscribeOn(Schedulers.io()); + } + + public static String getResponseBody(OkHttpClient client, Request request) throws NetworkErrorException { + return getResponseBody(client, request, true); + } + + private static String getResponseBody(OkHttpClient client, Request request, boolean retry) throws NetworkErrorException { + Response response = null; + try { + response = client.newCall(request).execute(); + if (response.isSuccessful()) { + byte[] bodybytes = response.body().bytes(); + String body = new String(bodybytes); + Matcher m = Pattern.compile("charset=([\\w\\-]+)").matcher(body); + if (m.find()) { + body = new String(bodybytes, m.group(1)); + } + return body; + } else if (retry) + return getResponseBody(client, request, false); + } catch (Exception e) { + e.printStackTrace(); + if (retry) + return getResponseBody(client, request, false); + } finally { + if (response != null) { + response.close(); + } + } + throw new NetworkErrorException(); + } + + public static class ParseErrorException extends Exception { + } + + public static class NetworkErrorException extends Exception { + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/Storage.java b/app/src/main/java/com/hiroshi/cimoc/core/Storage.java new file mode 100644 index 00000000..42fd7d1c --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/core/Storage.java @@ -0,0 +1,174 @@ +package com.hiroshi.cimoc.core; + +import android.content.ContentResolver; +import android.content.Context; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Environment; + +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.saf.DocumentFile; +import com.hiroshi.cimoc.utils.DecryptionUtils; +import com.hiroshi.cimoc.utils.DocumentUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import rx.Observable; +import rx.Subscriber; +import rx.schedulers.Schedulers; + +/** + * Created by Hiroshi on 2016/10/16. + */ + +public class Storage { + + private static String DOWNLOAD = "download"; + private static String PICTURE = "picture"; + private static String BACKUP = "backup"; + + public static DocumentFile initRoot(Context context, String uri) { + if (uri == null) { + File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "Cimoc"); + if (file.exists() || file.mkdirs()) { + return DocumentFile.fromFile(file); + } else { + return null; + } + } else if (uri.startsWith("content")) { + return DocumentFile.fromTreeUri(context, Uri.parse(uri)); + } else if (uri.startsWith("file")) { + return DocumentFile.fromFile(new File(Uri.parse(uri).getPath())); + } else { + return DocumentFile.fromFile(new File(uri, "Cimoc")); + } + } + + private static boolean copyFile(ContentResolver resolver, DocumentFile src, + DocumentFile parent, Subscriber subscriber) { + DocumentFile file = DocumentUtils.getOrCreateFile(parent, src.getName()); + if (file != null) { + subscriber.onNext(StringUtils.format("正在移动 %s...", src.getUri().getLastPathSegment())); + try { + DocumentUtils.writeBinaryToFile(resolver, src, file); + return true; + } catch (IOException e) { + e.printStackTrace(); + } + } + return false; + } + + private static boolean copyDir(ContentResolver resolver, DocumentFile src, + DocumentFile parent, Subscriber subscriber) { + if (src.isDirectory()) { + DocumentFile dir = DocumentUtils.getOrCreateSubDirectory(parent, src.getName()); + for (DocumentFile file : src.listFiles()) { + if (file.isDirectory()) { + if (!copyDir(resolver, file, dir, subscriber)) { + return false; + } + } else if (!copyFile(resolver, file, dir, subscriber)) { + return false; + } + } + } + return true; + } + + private static boolean copyDir(ContentResolver resolver, DocumentFile src, + DocumentFile dst, String name, Subscriber subscriber) { + DocumentFile file = src.findFile(name); + if (file != null && file.isDirectory()) { + return copyDir(resolver, file, dst, subscriber); + } + return true; + } + + private static void deleteDir(DocumentFile parent, String name, Subscriber subscriber) { + DocumentFile file = parent.findFile(name); + if (file != null && file.isDirectory()) { + subscriber.onNext(StringUtils.format("正在删除 %s", file.getUri().getLastPathSegment())); + file.delete(); + } + } + + private static boolean isDirSame(DocumentFile root, DocumentFile dst) { + return root.getUri().getScheme().equals("file") && dst.getUri().getPath().endsWith("primary:Cimoc") || + root.getUri().getPath().equals(dst.getUri().getPath()); + } + + public static Observable moveRootDir(final ContentResolver resolver, final DocumentFile root, final DocumentFile dst) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + if (dst.canRead() && !isDirSame(root, dst)) { + root.refresh(); + if (copyDir(resolver, root, dst, BACKUP, subscriber) && + copyDir(resolver, root, dst, DOWNLOAD, subscriber) && + copyDir(resolver, root, dst, PICTURE, subscriber)) { + deleteDir(root, BACKUP, subscriber); + deleteDir(root, DOWNLOAD, subscriber); + deleteDir(root, PICTURE, subscriber); + subscriber.onCompleted(); + } + } + subscriber.onError(new Exception()); + } + }).subscribeOn(Schedulers.io()); + } + + public static Observable savePicture(final ContentResolver resolver, final DocumentFile root, + final InputStream stream, final String filename) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + try { + DocumentFile dir = DocumentUtils.getOrCreateSubDirectory(root, PICTURE); + if (dir != null) { + DocumentFile file = DocumentUtils.getOrCreateFile(dir, filename); + DocumentUtils.writeBinaryToFile(resolver, file, stream); + subscriber.onNext(file.getUri()); + subscriber.onCompleted(); + } + } catch (IOException e) { + e.printStackTrace(); + } + subscriber.onError(new Exception()); + } + }).subscribeOn(Schedulers.io()); + } + + public static List buildImageUrlFromDocumentFile(List list, String chapter, int max) { + int count = 0; + List result = new ArrayList<>(list.size()); + for (DocumentFile file : list) { + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inJustDecodeBounds = true; + try { + BitmapFactory.decodeStream(file.openInputStream(), null, opts); + String uri = file.getUri().toString(); + if (uri.startsWith("file")) { // content:// 解码会出错 file:// 中文路径如果不解码 Fresco 读取不了 + uri = DecryptionUtils.urlDecrypt(uri); + } + ImageUrl image = new ImageUrl(++count, uri, false); + image.setHeight(opts.outHeight); + image.setWidth(opts.outWidth); + image.setChapter(chapter); + result.add(image); + } catch (Exception e) { + e.printStackTrace(); + } + if (count >= max) { + break; + } + } + return result; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/Update.java b/app/src/main/java/com/hiroshi/cimoc/core/Update.java new file mode 100644 index 00000000..c8be410d --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/core/Update.java @@ -0,0 +1,111 @@ +package com.hiroshi.cimoc.core; + +//import com.alibaba.fastjson.JSONObject; +import com.hiroshi.cimoc.App; + +import org.json.JSONObject; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import rx.Observable; +import rx.Subscriber; +import rx.schedulers.Schedulers; + +//import com.azhon.appupdate.config.UpdateConfiguration; +//import com.azhon.appupdate.manager.DownloadManager; + +/** + * Created by Hiroshi on 2016/8/24. + */ +public class Update { + + private static final String UPDATE_URL = "https://api.github.com/repos/feilongfl/cimoc/releases/latest"; + private static final String SERVER_FILENAME = "tag_name"; +// private static final String LIST = "list"; + + public static Observable check() { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + OkHttpClient client = App.getHttpClient(); + Request request = new Request.Builder().url(UPDATE_URL).build(); + Response response = null; + try { + response = client.newCall(request).execute(); + if (response.isSuccessful()) { + String json = response.body().string(); +// JSONObject object = new JSONObject(json).getJSONArray(LIST).getJSONObject(0); + String version = new JSONObject(json).getString(SERVER_FILENAME); + subscriber.onNext(version); + subscriber.onCompleted(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (response != null) { + response.close(); + } + } + subscriber.onError(new Exception()); + } + }).subscribeOn(Schedulers.io()); + } + + +// @SuppressLint("DefaultLocale") +// public static boolean update(Context context) { +// try { +//// DownloadManager.getInstance().release(); +// JSONObject updateObject = JSON.parseObject(Update.getUpdateJson()); +// JSONObject updateAssetsObject = updateObject.getJSONArray(ASSETS).getJSONObject(0); +// +// UpdateConfiguration configuration = new UpdateConfiguration() +// //输出错误日志 +// .setEnableLog(true) +// //设置自定义的下载 +// //.setHttpManager() +// //下载完成自动跳动安装页面 +// .setJumpInstallPage(true) +// //设置对话框背景图片 (图片规范参照demo中的示例图) +// .setDialogImage(R.drawable.ic_dialog_download_top_3) +// //设置按钮的颜色 +// .setDialogButtonColor(Color.parseColor("#39c1e9")) +// //设置按钮的文字颜色 +// .setDialogButtonTextColor(Color.WHITE) +// //支持断点下载 +// .setBreakpointDownload(true) +// //设置是否显示通知栏进度 +// .setShowNotification(true) +// //设置强制更新 +// .setForcedUpgrade(false); +// +// DownloadManager manager = DownloadManager.getInstance(context); +// manager.setApkName("Comic." + updateObject.getString(NAME) + ".release.apk") +// .setApkUrl(updateAssetsObject.getString("browser_download_url")) +// .setDownloadPath(Environment.getExternalStorageDirectory() + "/Download") +// .setApkDescription(updateObject.getString("body")) +// .setSmallIcon(R.mipmap.ic_launcher_blue_foreground) +// .setShowNewerToast(true) +// .setConfiguration(configuration) +// .setApkVersionCode(2) +// .setApkVersionName(updateObject.getString(TAG_NAME).substring(1)); +// +// if (App.getUpdateCurrentUrl().equals(Constants.UPDATE_GITEE_URL)) { +// manager.download(); +// } else { +// manager.setApkSize(String.format("%.2f", updateAssetsObject.getDouble("size") / (1024 * 1024))) +// .download(); +// } +// +// return true; +// } catch (Exception e) { +// e.printStackTrace(); +// return false; +// } +// } +// +// public static String getUpdateJson() { +// return updateJson; +// } +} diff --git a/app/src/main/java/com/hiroshi/cimoc/core/base/Manga.java b/app/src/main/java/com/hiroshi/cimoc/core/base/Manga.java deleted file mode 100644 index d18f843a..00000000 --- a/app/src/main/java/com/hiroshi/cimoc/core/base/Manga.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.hiroshi.cimoc.core.base; - -import com.hiroshi.cimoc.CimocApplication; -import com.hiroshi.cimoc.model.Chapter; -import com.hiroshi.cimoc.model.Comic; -import com.hiroshi.cimoc.model.EventMessage; - -import org.greenrobot.eventbus.EventBus; - -import java.io.IOException; -import java.util.List; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - -/** - * Created by Hiroshi on 2016/7/8. - */ -public abstract class Manga { - - private OkHttpClient client; - - protected int source; - protected String host; - - public Manga(int source, String host) { - this.source = source; - this.host = host; - this.client = CimocApplication.getHttpClient(); - } - - public void search(String keyword, int page) { - Request request = buildSearchRequest(keyword, page); - if (request == null) { - EventBus.getDefault().post(new EventMessage(EventMessage.SEARCH_FAIL, null)); - } else { - enqueueClient(request, new OnResponseSuccessHandler() { - @Override - public void onSuccess(String html) { - List list = parseSearch(html); - if (list == null || list.isEmpty()) { - EventBus.getDefault().post(new EventMessage(EventMessage.SEARCH_FAIL, null)); - } else { - EventBus.getDefault().post(new EventMessage(EventMessage.SEARCH_SUCCESS, list)); - } - } - }); - } - } - - public void into(final Comic comic) { - enqueueClient(buildIntoRequest(comic.getCid()), new OnResponseSuccessHandler() { - @Override - public void onSuccess(String html) { - List list = parseInto(html, comic); - if (list == null || list.isEmpty()) { - EventBus.getDefault().post(new EventMessage(EventMessage.LOAD_COMIC_FAIL, null)); - } else { - EventBus.getDefault().post(new EventMessage(EventMessage.LOAD_COMIC_SUCCESS, list)); - } - } - }); - } - - public void browse(String cid, String path) { - enqueueClient(buildBrowseRequest(cid, path), new OnResponseSuccessHandler() { - @Override - public void onSuccess(String html) { - String[] images = parseBrowse(html); - if (images == null) { - EventBus.getDefault().post(new EventMessage(EventMessage.PARSE_PIC_FAIL, null)); - } else { - EventBus.getDefault().post(new EventMessage(EventMessage.PARSE_PIC_SUCCESS, images)); - } - } - }); - } - - public void cancel() { - client.dispatcher().cancelAll(); - } - - private void enqueueClient(Request request, final OnResponseSuccessHandler handler) { - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - EventBus.getDefault().post(new EventMessage(EventMessage.NETWORK_ERROR, null)); - } - @Override - public void onResponse(Call call, Response response) throws IOException { - if (response.isSuccessful()) { - handler.onSuccess(response.body().string()); - } else { - EventBus.getDefault().post(new EventMessage(EventMessage.NETWORK_ERROR, null)); - } - } - }); - } - - protected String execute(String url) { - Request request = new Request.Builder().url(url).build(); - try { - Response response = client.newCall(request).execute(); - if (response.isSuccessful()) { - return response.body().string(); - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - protected abstract Request buildSearchRequest(String keyword, int page); - - protected abstract List parseSearch(String html); - - protected abstract Request buildIntoRequest(String cid); - - protected abstract List parseInto(String html, Comic comic); - - protected abstract Request buildBrowseRequest(String cid, String path); - - protected abstract String[] parseBrowse(String html); - - private interface OnResponseSuccessHandler { - void onSuccess(String html); - } - -} diff --git a/app/src/main/java/com/hiroshi/cimoc/fresco/ControllerBuilderProvider.java b/app/src/main/java/com/hiroshi/cimoc/fresco/ControllerBuilderProvider.java new file mode 100644 index 00000000..2abf81d9 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/fresco/ControllerBuilderProvider.java @@ -0,0 +1,61 @@ +package com.hiroshi.cimoc.fresco; + +import android.content.Context; +import android.util.SparseArray; + +import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder; +import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilderSupplier; +import com.facebook.imagepipeline.core.ImagePipeline; +import com.facebook.imagepipeline.core.ImagePipelineFactory; +import com.hiroshi.cimoc.manager.SourceManager; + +/** + * Created by Hiroshi on 2016/9/5. + */ +public class ControllerBuilderProvider { + + private Context mContext; + private SparseArray mSupplierArray; + private SparseArray mPipelineArray; + private SourceManager.HeaderGetter mHeaderGetter; + private boolean mCover; + + public ControllerBuilderProvider(Context context, SourceManager.HeaderGetter getter, boolean cover) { + mSupplierArray = new SparseArray<>(); + mPipelineArray = new SparseArray<>(); + mContext = context; + mHeaderGetter = getter; + mCover = cover; + } + + public PipelineDraweeControllerBuilder get(int type) { + PipelineDraweeControllerBuilderSupplier supplier = mSupplierArray.get(type); + if (supplier == null) { + ImagePipelineFactory factory = ImagePipelineFactoryBuilder + .build(mContext, type < 0 ? null : mHeaderGetter.getHeader(type), mCover); + supplier = ControllerBuilderSupplierFactory.get(mContext, factory); + mSupplierArray.put(type, supplier); + mPipelineArray.put(type, factory.getImagePipeline()); + } + return supplier.get(); + } + + public void pause() { + for (int i = 0; i != mPipelineArray.size(); ++i) { + mPipelineArray.valueAt(i).pause(); + } + } + + public void resume() { + for (int i = 0; i != mPipelineArray.size(); ++i) { + mPipelineArray.valueAt(i).resume(); + } + } + + public void clear() { + for (int i = 0; i != mPipelineArray.size(); ++i) { + mPipelineArray.valueAt(i).clearMemoryCaches(); + } + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/fresco/ControllerBuilderSupplierFactory.java b/app/src/main/java/com/hiroshi/cimoc/fresco/ControllerBuilderSupplierFactory.java new file mode 100644 index 00000000..e9f6caf0 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/fresco/ControllerBuilderSupplierFactory.java @@ -0,0 +1,25 @@ +package com.hiroshi.cimoc.fresco; + +import android.content.Context; + +import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder; +import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilderSupplier; +import com.facebook.imagepipeline.core.ImagePipelineFactory; + +import okhttp3.Headers; + +/** + * Created by Hiroshi on 2016/9/5. + */ +public class ControllerBuilderSupplierFactory { + + public static PipelineDraweeControllerBuilderSupplier get(Context context, ImagePipelineFactory factory) { + return new PipelineDraweeControllerBuilderSupplier(context.getApplicationContext(), factory, null); + } + + public static PipelineDraweeControllerBuilder get(Context context, Headers header) { + ImagePipelineFactory factory = ImagePipelineFactoryBuilder.build(context, header, false); + return new PipelineDraweeControllerBuilderSupplier(context.getApplicationContext(), factory, null).get(); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/fresco/ImagePipelineFactoryBuilder.java b/app/src/main/java/com/hiroshi/cimoc/fresco/ImagePipelineFactoryBuilder.java new file mode 100644 index 00000000..e6e7de92 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/fresco/ImagePipelineFactoryBuilder.java @@ -0,0 +1,28 @@ +package com.hiroshi.cimoc.fresco; + +import android.content.Context; +import android.graphics.Bitmap; + +import com.facebook.imagepipeline.core.ImagePipelineConfig; +import com.facebook.imagepipeline.core.ImagePipelineFactory; +import com.hiroshi.cimoc.App; + +import okhttp3.Headers; + +/** + * Created by Hiroshi on 2016/7/8. + */ +public class ImagePipelineFactoryBuilder { + + public static ImagePipelineFactory build(Context context, Headers header, boolean down) { + ImagePipelineConfig.Builder builder = + ImagePipelineConfig.newBuilder(context.getApplicationContext()) + .setDownsampleEnabled(down) + .setBitmapsConfig(down ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888); + if (header != null) { + builder.setNetworkFetcher(new OkHttpNetworkFetcher(App.getHttpClient(), header)); + } + return new ImagePipelineFactory(builder.build()); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/fresco/OkHttpNetworkFetcher.java b/app/src/main/java/com/hiroshi/cimoc/fresco/OkHttpNetworkFetcher.java new file mode 100644 index 00000000..e3d8d430 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/fresco/OkHttpNetworkFetcher.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.hiroshi.cimoc.fresco; + +import android.net.Uri; +import android.os.Looper; +import android.os.SystemClock; + +import com.facebook.common.logging.FLog; +import com.facebook.imagepipeline.image.EncodedImage; +import com.facebook.imagepipeline.producers.BaseNetworkFetcher; +import com.facebook.imagepipeline.producers.BaseProducerContextCallbacks; +import com.facebook.imagepipeline.producers.Consumer; +import com.facebook.imagepipeline.producers.FetchState; +import com.facebook.imagepipeline.producers.ProducerContext; +import com.hiroshi.cimoc.App; +import com.hiroshi.cimoc.ui.widget.CustomToast; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; + +import okhttp3.CacheControl; +import okhttp3.Call; +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +/** + * Network fetcher that uses OkHttp 3 as a backend. + */ +public class OkHttpNetworkFetcher extends + BaseNetworkFetcher { + + private static final String TAG = "OkHttpNetworkFetchProducer"; + private static final String QUEUE_TIME = "queue_time"; + private static final String FETCH_TIME = "fetch_time"; + private static final String TOTAL_TIME = "total_time"; + private static final String IMAGE_SIZE = "image_size"; + private final OkHttpClient mOkHttpClient; + private Executor mCancellationExecutor; + private Headers mHeaders; + + /** + * @param okHttpClient client to use + */ + public OkHttpNetworkFetcher(OkHttpClient okHttpClient, Headers headers) { + mOkHttpClient = okHttpClient; + + //修复打开仅WiFi联网功能后运行闪退的问题 + try { + mCancellationExecutor = okHttpClient.dispatcher().executorService(); + } catch (NullPointerException e) { + CustomToast.showToast(App.getActivity(), "网络连接失败,请检查网络!!", 2000); + } + + mHeaders = headers; + } + + @Override + public OkHttpNetworkFetchState createFetchState( + Consumer consumer, + ProducerContext context) { + return new OkHttpNetworkFetchState(consumer, context); + } + + @Override + public void fetch(final OkHttpNetworkFetchState fetchState, final Callback callback) { + fetchState.submitTime = SystemClock.elapsedRealtime(); + final Uri uri = fetchState.getUri(); + Request request = new Request.Builder() + .cacheControl(new CacheControl.Builder().noStore().build()) + .headers(mHeaders) + .url(uri.toString()) + .get() + .build(); + final Call call = mOkHttpClient.newCall(request); + + fetchState.getContext().addCallbacks( + new BaseProducerContextCallbacks() { + @Override + public void onCancellationRequested() { + if (Looper.myLooper() != Looper.getMainLooper()) { + call.cancel(); + } else { + mCancellationExecutor.execute(new Runnable() { + @Override + public void run() { + call.cancel(); + } + }); + } + } + }); + + call.enqueue( + new okhttp3.Callback() { + @Override + public void onResponse(Call call, Response response) throws IOException { + fetchState.responseTime = SystemClock.elapsedRealtime(); + final ResponseBody body = response.body(); + try { + if (!response.isSuccessful()) { + handleException( + call, + new IOException("Unexpected HTTP code " + response), + callback); + return; + } + + long contentLength = body.contentLength(); + if (contentLength < 0) { + contentLength = 0; + } + callback.onResponse(body.byteStream(), (int) contentLength); + } catch (Exception e) { + handleException(call, e, callback); + } finally { + try { + body.close(); + } catch (Exception e) { + FLog.w(TAG, "Exception when closing response body", e); + } + } + } + + @Override + public void onFailure(Call call, IOException e) { + handleException(call, e, callback); + } + }); + } + + @Override + public void onFetchCompletion(OkHttpNetworkFetchState fetchState, int byteSize) { + fetchState.fetchCompleteTime = SystemClock.elapsedRealtime(); + } + + @Override + public Map getExtraMap(OkHttpNetworkFetchState fetchState, int byteSize) { + Map extraMap = new HashMap<>(4); + extraMap.put(QUEUE_TIME, Long.toString(fetchState.responseTime - fetchState.submitTime)); + extraMap.put(FETCH_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.responseTime)); + extraMap.put(TOTAL_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.submitTime)); + extraMap.put(IMAGE_SIZE, Integer.toString(byteSize)); + return extraMap; + } + + /** + * Handles exceptions. + *

+ *

OkHttp notifies callers of cancellations via an IOException. If IOException is caught + * after request cancellation, then the exception is interpreted as successful cancellation + * and onCancellation is called. Otherwise onFailure is called. + */ + private void handleException(final Call call, final Exception e, final Callback callback) { + if (call.isCanceled()) { + callback.onCancellation(); + } else { + callback.onFailure(e); + } + } + + public static class OkHttpNetworkFetchState extends FetchState { + public long submitTime; + public long responseTime; + public long fetchCompleteTime; + + public OkHttpNetworkFetchState( + Consumer consumer, + ProducerContext producerContext) { + super(consumer, producerContext); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hiroshi/cimoc/fresco/processor/BinaryPostprocessor.java b/app/src/main/java/com/hiroshi/cimoc/fresco/processor/BinaryPostprocessor.java new file mode 100644 index 00000000..47277b84 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/fresco/processor/BinaryPostprocessor.java @@ -0,0 +1,59 @@ +package com.hiroshi.cimoc.fresco.processor; + +import android.graphics.Bitmap; + +import com.facebook.cache.common.CacheKey; +import com.facebook.cache.common.SimpleCacheKey; +import com.facebook.common.references.CloseableReference; +import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory; +import com.facebook.imagepipeline.request.BasePostprocessor; + +/** + * 二值化处理 仅用于测试 + * Created by Hiroshi on 2017/1/10. + */ + +public class BinaryPostprocessor extends BasePostprocessor { + + private String url; + + public BinaryPostprocessor(String url) { + this.url = url; + } + + @Override + public CloseableReference process(Bitmap sourceBitmap, PlatformBitmapFactory bitmapFactory) { + int width = sourceBitmap.getWidth(); + int height = sourceBitmap.getHeight(); + + int[] pixels = new int[width * height]; + sourceBitmap.getPixels(pixels, 0, width, 0, 0, width, height); + + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { + int pixel = pixels[i * width + j]; + int red = ((pixel & 0x00FF0000) >> 16); + int green = ((pixel & 0x0000FF00) >> 8); + int blue = (pixel & 0x000000FF); + int gray = red * 30 + green * 59 + blue * 11; + pixels[i * width + j] = gray > 21500 ? 0xFFFFFF : 0x000000; + } + } + + CloseableReference reference = bitmapFactory.createBitmap(width, height, Bitmap.Config.RGB_565); + try { + Bitmap bitmap = reference.get(); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return CloseableReference.cloneOrNull(reference); + } finally { + CloseableReference.closeSafely(reference); + } + } + + @Override + public CacheKey getPostprocessorCacheKey() { + return new SimpleCacheKey(url.concat("binary")); + } + +} + diff --git a/app/src/main/java/com/hiroshi/cimoc/fresco/processor/MangaPostprocessor.java b/app/src/main/java/com/hiroshi/cimoc/fresco/processor/MangaPostprocessor.java new file mode 100644 index 00000000..7841d0e2 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/fresco/processor/MangaPostprocessor.java @@ -0,0 +1,249 @@ +package com.hiroshi.cimoc.fresco.processor; + +import android.graphics.Bitmap; + +import com.facebook.cache.common.CacheKey; +import com.facebook.cache.common.SimpleCacheKey; +import com.facebook.common.references.CloseableReference; +import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory; +import com.facebook.imagepipeline.request.BasePostprocessor; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.rx.RxBus; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.utils.StringUtils; + +/** + * Created by Hiroshi on 2017/3/3. + */ + +public class MangaPostprocessor extends BasePostprocessor { + + private ImageUrl mImage; + private boolean isPaging; + private boolean isPagingReverse; + private boolean isWhiteEdge; + + private int mWidth, mHeight; + private int mPosX, mPosY; + private boolean isDone = false; + + public MangaPostprocessor(ImageUrl image, boolean paging, boolean pagingReverse, boolean whiteEdge) { + mImage = image; + isPaging = paging; + isPagingReverse = pagingReverse; + isWhiteEdge = whiteEdge; + } + + @Override + public CloseableReference process(Bitmap sourceBitmap, PlatformBitmapFactory bitmapFactory) { + mWidth = sourceBitmap.getWidth(); + mHeight = sourceBitmap.getHeight(); + + if (isPaging) { + preparePaging(isPagingReverse); + isDone = true; + } + if (isWhiteEdge) { + prepareWhiteEdge(sourceBitmap); + isDone = true; + } + + if (isDone) { + CloseableReference reference = bitmapFactory.createBitmap(mWidth, mHeight, Bitmap.Config.RGB_565); + try { + processing(sourceBitmap, reference.get()); + return CloseableReference.cloneOrNull(reference); + } finally { + CloseableReference.closeSafely(reference); + } + } + return super.process(sourceBitmap, bitmapFactory); + } + + private void preparePaging(boolean reverse) { + if (needHorizontalPaging()) { + int mBase = 2; + mWidth = mWidth / mBase; + mImage.nextState(); + if(mImage.getState() < mBase) { + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_PICTURE_PAGING, mImage)); + } + + mPosY = 0; + mPosX = reverse? (mBase - mImage.getState()) : (mImage.getState()-1); + mPosX *= mWidth; + } else if (needVerticalPaging()) { + int mBase = mHeight / (mWidth * 2); + mHeight = mHeight / mBase; + mImage.nextState(); + if(mImage.getState() < mBase) { + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_PICTURE_PAGING, mImage)); + } + + mPosX = 0; + mPosY = reverse? (mBase - mImage.getState()) : (mImage.getState()-1); + mPosY *= mHeight; + } + } + + private boolean needVerticalPaging() { + return mHeight > 3 * mWidth; + } + + private boolean needHorizontalPaging() { + return mWidth > 1.2 * mHeight; + } + + private void prepareWhiteEdge(Bitmap bitmap) { + int y1, y2, x1, x2; + int[] pixels = new int[(mWidth > mHeight ? mWidth : mHeight) * 20]; + int limit = mPosY + mHeight / 3; + + for (y1 = mPosY; y1 < limit; ++y1) { + // 确定上线 y1 + bitmap.getPixels(pixels, 0, mWidth, mPosX, y1, mWidth, 1); + if (!oneDimensionScan(pixels, mWidth)) { + bitmap.getPixels(pixels, 0, mWidth, 0, y1, mWidth, 10); + if (!twoDimensionScan(pixels, mWidth, false, false)) { + break; + } + y1 += 9; + } + } + + limit = mPosY + mHeight * 2 / 3; + + for (y2 = mPosY + mHeight - 1; y2 > limit; --y2) { + // 确定下线 y2 + bitmap.getPixels(pixels, 0, mWidth, mPosX, y2, mWidth, 1); + if (!oneDimensionScan(pixels, mWidth)) { + bitmap.getPixels(pixels, 0, mWidth, 0, y2 - 9, mWidth, 10); + if (!twoDimensionScan(pixels, mWidth, false, true)) { + break; + } + y2 -= 9; + } + } + + int h = y2 - y1 + 1; + limit = mPosX + mWidth / 3; + + for (x1 = mPosX; x1 < limit; ++x1) { + // 确定左线 x1 + bitmap.getPixels(pixels, 0, 1, x1, y1, 1, h); + if (!oneDimensionScan(pixels, h)) { + bitmap.getPixels(pixels, 0, 10, x1, y1, 10, h); + if (!twoDimensionScan(pixels, h, true, false)) { + break; + } + x1 += 9; + } + } + + limit = mPosX + mWidth * 2 / 3; + + for (x2 = mPosX + mWidth - 1; x2 > limit; --x2) { + // 确定右线 x2 + bitmap.getPixels(pixels, 0, 1, x2, y1, 1, h); + if (!oneDimensionScan(pixels, h)) { + bitmap.getPixels(pixels, 0, 10, x2 - 9, y1, 10, h); + if (!twoDimensionScan(pixels, h, true, true)) { + break; + } + x2 -= 9; + } + } + + mWidth = x2 - x1; + mHeight = y2 - y1; + mPosX = x1; + mPosY = y1; + } + + private void processing(Bitmap src, Bitmap dst) { + int unit = mHeight / 20; + int remain = mHeight - 20 * unit; + int[] pixels = new int[(remain > unit ? remain : unit) * mWidth]; + for (int j = 0; j < 20; ++j) { + src.getPixels(pixels, 0, mWidth, mPosX, mPosY + j * unit, mWidth, unit); + dst.setPixels(pixels, 0, mWidth, 0, j * unit, mWidth, unit); + } + if (remain > 0) { + src.getPixels(pixels, 0, mWidth, mPosX, mPosY + 20 * unit, mWidth, remain); + dst.setPixels(pixels, 0, mWidth, 0, 20 * unit, mWidth, remain); + } + } + + @Override + public CacheKey getPostprocessorCacheKey() { + return new SimpleCacheKey(StringUtils.format("%s-post-%d", mImage.getUrl(), mImage.getId())); + } + + /** + * @return 全白返回 true + */ + private boolean oneDimensionScan(int[] pixels, int length) { + for (int i = 0; i < length; ++i) { + if (!isWhite(pixels[i])) { + return false; + } + } + return true; + } + + /** + * 10 * 20 方格 按 2:3:3:2 划分为四个区域 权值分别为 0 1 2 3 + * + * @return 加权值 > 60 返回 false + */ + private boolean twoDimensionScan(int[] pixels, int length, boolean vertical, boolean reverse) { + if (length < 20) { + return false; + } + + int[] value = new int[20]; + int result = 0; + for (int i = 0; i < length; ++i) { + if (result > 60) { + return false; + } + result -= value[i % 20]; + value[i % 20] = 0; + for (int j = 0; j < 10; ++j) { + int k = vertical ? (i * 10 + j) : (j * length + i); + value[i % 20] += getValue(isWhite(pixels[k]), reverse, j); + } + result += value[i % 20]; + } + return true; + } + + /** + * 根据方向位置计算权值 + */ + private int getValue(boolean white, boolean reverse, int pos) { + if (white) { + return 0; + } + if (pos < 2) { + return reverse ? 3 : 0; + } else if (pos < 5) { + return reverse ? 2 : 1; + } else if (pos < 8) { + return reverse ? 1 : 2; + } + return reverse ? 0 : 3; + } + + /** + * 固定阈值 根据灰度判断黑白 + */ + private boolean isWhite(int pixel) { + int red = ((pixel & 0x00FF0000) >> 16); + int green = ((pixel & 0x0000FF00) >> 8); + int blue = (pixel & 0x000000FF); + int gray = red * 30 + green * 59 + blue * 11; + return gray > 21500; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/global/ClickEvents.java b/app/src/main/java/com/hiroshi/cimoc/global/ClickEvents.java new file mode 100644 index 00000000..29710116 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/global/ClickEvents.java @@ -0,0 +1,182 @@ +package com.hiroshi.cimoc.global; + +import android.content.Context; + +import com.hiroshi.cimoc.R; +import com.hiroshi.cimoc.manager.PreferenceManager; + +/** + * Created by Hiroshi on 2016/10/9. + */ + +public class ClickEvents { + + public static final int EVENT_NULL = 0; + public static final int EVENT_PREV_PAGE = 1; + public static final int EVENT_NEXT_PAGE = 2; + public static final int EVENT_SAVE_PICTURE = 3; + public static final int EVENT_LOAD_PREV = 4; + public static final int EVENT_LOAD_NEXT = 5; + public static final int EVENT_EXIT_READER = 6; + public static final int EVENT_TO_FIRST = 7; + public static final int EVENT_TO_LAST = 8; + public static final int EVENT_SWITCH_SCREEN = 9; + public static final int EVENT_SWITCH_MODE = 10; + public static final int EVENT_SWITCH_CONTROL = 11; + public static final int EVENT_RELOAD_IMAGE = 12; + public static final int EVENT_SWITCH_NIGHT = 13; + + private static String[] mEventTitle; + + public enum JoyLocks { + LT, + RT + } + + public static String[] getPageClickEvents() { + return new String[]{ + PreferenceManager.PREF_READER_PAGE_CLICK_LEFT, + PreferenceManager.PREF_READER_PAGE_CLICK_TOP, + PreferenceManager.PREF_READER_PAGE_CLICK_MIDDLE, + PreferenceManager.PREF_READER_PAGE_CLICK_BOTTOM, + PreferenceManager.PREF_READER_PAGE_CLICK_RIGHT, + PreferenceManager.PREF_READER_PAGE_CLICK_UP, + PreferenceManager.PREF_READER_PAGE_CLICK_DOWN, + //joy + PreferenceManager.PREF_READER_PAGE_JOY_LT, + PreferenceManager.PREF_READER_PAGE_JOY_RT, + PreferenceManager.PREF_READER_PAGE_JOY_LEFT, + PreferenceManager.PREF_READER_PAGE_JOY_RIGHT, + PreferenceManager.PREF_READER_PAGE_JOY_UP, + PreferenceManager.PREF_READER_PAGE_JOY_DOWN, + PreferenceManager.PREF_READER_PAGE_JOY_B, + PreferenceManager.PREF_READER_PAGE_JOY_A, + PreferenceManager.PREF_READER_PAGE_JOY_X, + PreferenceManager.PREF_READER_PAGE_JOY_Y, + }; + } + + public static String[] getPageLongClickEvents() { + return new String[]{PreferenceManager.PREF_READER_PAGE_LONG_CLICK_LEFT, PreferenceManager.PREF_READER_PAGE_LONG_CLICK_TOP, + PreferenceManager.PREF_READER_PAGE_LONG_CLICK_MIDDLE, PreferenceManager.PREF_READER_PAGE_LONG_CLICK_BOTTOM, + PreferenceManager.PREF_READER_PAGE_LONG_CLICK_RIGHT}; + } + + public static int[] getPageClickEventChoice(PreferenceManager manager) { + final int[] array = { + //screen + manager.getInt(PreferenceManager.PREF_READER_PAGE_CLICK_LEFT, EVENT_PREV_PAGE), + manager.getInt(PreferenceManager.PREF_READER_PAGE_CLICK_TOP, EVENT_PREV_PAGE), + manager.getInt(PreferenceManager.PREF_READER_PAGE_CLICK_MIDDLE, EVENT_SWITCH_CONTROL), + manager.getInt(PreferenceManager.PREF_READER_PAGE_CLICK_BOTTOM, EVENT_NEXT_PAGE), + manager.getInt(PreferenceManager.PREF_READER_PAGE_CLICK_RIGHT, EVENT_NEXT_PAGE), + //key + manager.getInt(PreferenceManager.PREF_READER_PAGE_CLICK_UP, EVENT_PREV_PAGE), + manager.getInt(PreferenceManager.PREF_READER_PAGE_CLICK_DOWN, EVENT_NEXT_PAGE), + //joy + manager.getInt(PreferenceManager.PREF_READER_PAGE_JOY_LT, EVENT_PREV_PAGE), + manager.getInt(PreferenceManager.PREF_READER_PAGE_JOY_RT, EVENT_NEXT_PAGE), + manager.getInt(PreferenceManager.PREF_READER_PAGE_JOY_LEFT, EVENT_PREV_PAGE), + manager.getInt(PreferenceManager.PREF_READER_PAGE_JOY_RIGHT, EVENT_NEXT_PAGE), + manager.getInt(PreferenceManager.PREF_READER_PAGE_JOY_UP, EVENT_LOAD_PREV), + manager.getInt(PreferenceManager.PREF_READER_PAGE_JOY_DOWN, EVENT_LOAD_NEXT), + manager.getInt(PreferenceManager.PREF_READER_PAGE_JOY_B, EVENT_EXIT_READER), + manager.getInt(PreferenceManager.PREF_READER_PAGE_JOY_A, EVENT_NULL), + manager.getInt(PreferenceManager.PREF_READER_PAGE_JOY_X, EVENT_SWITCH_CONTROL), + manager.getInt(PreferenceManager.PREF_READER_PAGE_JOY_Y, EVENT_SAVE_PICTURE), + }; + return array; + } + + public static int[] getPageLongClickEventChoice(PreferenceManager manager) { + int[] array = new int[5]; + array[0] = manager.getInt(PreferenceManager.PREF_READER_PAGE_LONG_CLICK_LEFT, 0); + array[1] = manager.getInt(PreferenceManager.PREF_READER_PAGE_LONG_CLICK_TOP, 0); + array[2] = manager.getInt(PreferenceManager.PREF_READER_PAGE_LONG_CLICK_MIDDLE, 0); + array[3] = manager.getInt(PreferenceManager.PREF_READER_PAGE_LONG_CLICK_BOTTOM, 0); + array[4] = manager.getInt(PreferenceManager.PREF_READER_PAGE_LONG_CLICK_RIGHT, 0); + return array; + } + + public static String[] getStreamClickEvents() { + return new String[]{ + //screen + PreferenceManager.PREF_READER_STREAM_CLICK_LEFT, + PreferenceManager.PREF_READER_STREAM_CLICK_TOP, + PreferenceManager.PREF_READER_STREAM_CLICK_MIDDLE, + PreferenceManager.PREF_READER_STREAM_CLICK_BOTTOM, + PreferenceManager.PREF_READER_STREAM_CLICK_RIGHT, + //key + PreferenceManager.PREF_READER_STREAM_CLICK_UP, + PreferenceManager.PREF_READER_STREAM_CLICK_DOWN, + //joy + PreferenceManager.PREF_READER_STREAM_JOY_LT, + PreferenceManager.PREF_READER_STREAM_JOY_RT, + PreferenceManager.PREF_READER_STREAM_JOY_LEFT, + PreferenceManager.PREF_READER_STREAM_JOY_RIGHT, + PreferenceManager.PREF_READER_STREAM_JOY_UP, + PreferenceManager.PREF_READER_STREAM_JOY_DOWN, + PreferenceManager.PREF_READER_STREAM_JOY_B, + PreferenceManager.PREF_READER_STREAM_JOY_A, + PreferenceManager.PREF_READER_STREAM_JOY_X, + PreferenceManager.PREF_READER_STREAM_JOY_Y, + }; + } + + public static String[] getStreamLongClickEvents() { + return new String[]{PreferenceManager.PREF_READER_STREAM_LONG_CLICK_LEFT, PreferenceManager.PREF_READER_STREAM_LONG_CLICK_TOP, + PreferenceManager.PREF_READER_STREAM_LONG_CLICK_MIDDLE, PreferenceManager.PREF_READER_STREAM_LONG_CLICK_BOTTOM, + PreferenceManager.PREF_READER_STREAM_LONG_CLICK_RIGHT}; + } + + public static int[] getStreamClickEventChoice(PreferenceManager manager) { + final int[] array = { + //screen + manager.getInt(PreferenceManager.PREF_READER_STREAM_CLICK_LEFT, EVENT_NULL),//0 + manager.getInt(PreferenceManager.PREF_READER_STREAM_CLICK_TOP, EVENT_NULL),//1 + manager.getInt(PreferenceManager.PREF_READER_STREAM_CLICK_MIDDLE, EVENT_SWITCH_CONTROL),//2 + manager.getInt(PreferenceManager.PREF_READER_STREAM_CLICK_BOTTOM, EVENT_NULL),//3 + manager.getInt(PreferenceManager.PREF_READER_STREAM_CLICK_RIGHT, EVENT_NULL),//4 + //key + manager.getInt(PreferenceManager.PREF_READER_STREAM_CLICK_UP, EVENT_NULL),//5 + manager.getInt(PreferenceManager.PREF_READER_STREAM_CLICK_DOWN, EVENT_NULL),//6 + //joy + manager.getInt(PreferenceManager.PREF_READER_STREAM_JOY_LT, EVENT_PREV_PAGE),//7 + manager.getInt(PreferenceManager.PREF_READER_STREAM_JOY_RT, EVENT_NEXT_PAGE),//8 + manager.getInt(PreferenceManager.PREF_READER_STREAM_JOY_LEFT, EVENT_NULL),//9 + manager.getInt(PreferenceManager.PREF_READER_STREAM_JOY_RIGHT, EVENT_NULL),//10 + manager.getInt(PreferenceManager.PREF_READER_STREAM_JOY_UP, EVENT_NULL),//11 + manager.getInt(PreferenceManager.PREF_READER_STREAM_JOY_DOWN, EVENT_NULL),//12 + manager.getInt(PreferenceManager.PREF_READER_STREAM_JOY_B, EVENT_NULL),//13 + manager.getInt(PreferenceManager.PREF_READER_STREAM_JOY_A, EVENT_NULL),//14 + manager.getInt(PreferenceManager.PREF_READER_STREAM_JOY_X, EVENT_SWITCH_CONTROL),//15 + manager.getInt(PreferenceManager.PREF_READER_STREAM_JOY_Y, EVENT_SAVE_PICTURE),//16 + }; + return array; + } + + public static int[] getStreamLongClickEventChoice(PreferenceManager manager) { + int[] array = new int[7]; + array[0] = manager.getInt(PreferenceManager.PREF_READER_STREAM_LONG_CLICK_LEFT, 0); + array[1] = manager.getInt(PreferenceManager.PREF_READER_STREAM_LONG_CLICK_TOP, 0); + array[2] = manager.getInt(PreferenceManager.PREF_READER_STREAM_LONG_CLICK_MIDDLE, 0); + array[3] = manager.getInt(PreferenceManager.PREF_READER_STREAM_LONG_CLICK_BOTTOM, 0); + array[4] = manager.getInt(PreferenceManager.PREF_READER_STREAM_LONG_CLICK_RIGHT, 0); + return array; + } + + public static String[] getEventTitleArray(Context context) { + if (mEventTitle == null) { + mEventTitle = context.getResources().getStringArray(R.array.event_items); + } + return mEventTitle; + } + + public static String getEventTitle(Context context, int value) { + if (mEventTitle == null) { + mEventTitle = context.getResources().getStringArray(R.array.event_items); + } + return mEventTitle[value]; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/global/Extra.java b/app/src/main/java/com/hiroshi/cimoc/global/Extra.java new file mode 100644 index 00000000..64ee2f79 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/global/Extra.java @@ -0,0 +1,23 @@ +package com.hiroshi.cimoc.global; + +/** + * Created by Hiroshi on 2016/12/25. + */ + +public class Extra { + + public static final String EXTRA_CHAPTER = "cimoc.intent.extra.EXTRA_CHAPTER"; + public static final String EXTRA_TASK = "cimoc.intent.extra.EXTRA_TASK"; + public static final String EXTRA_ID = "cimoc.intent.extra.EXTRA_ID"; + public static final String EXTRA_SOURCE = "cimoc.intent.extra.EXTRA_SOURCE"; + public static final String EXTRA_CID = "cimoc.intent.extra.EXTRA_CID"; + public static final String EXTRA_KEYWORD = "cimoc.intent.extra.EXTRA_KEYWORD"; + public static final String EXTRA_STRICT = "cimoc.intent.extra.EXTRA_STRICT"; + public static final String EXTRA_MODE = "cimoc.intent.extra.EXTRA_MODE"; + public static final String EXTRA_PICKER_PATH = "cimoc.intent.extra.EXTRA_PICKER_PATH"; + public static final String EXTRA_RESULT = "cimoc.intent.extra.EXTRA_RESULT"; + public static final String EXTRA_IS_LONG = "cimoc.intent.extra.EXTRA_IS_LONG"; + public static final String EXTRA_IS_PORTRAIT = "cimoc.intent.extra.EXTRA_IS_PORTRAIT"; + public static final String EXTRA_IS_STREAM = "cimoc.intent.extra.EXTRA_IS_STREAM"; + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/global/FastClick.java b/app/src/main/java/com/hiroshi/cimoc/global/FastClick.java new file mode 100644 index 00000000..7de56571 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/global/FastClick.java @@ -0,0 +1,20 @@ +package com.hiroshi.cimoc.global; + +/** + * Created by Hiroshi on 2016/12/1. + */ + +public class FastClick { + + private static long last = 0; + + public static boolean isClickValid() { + long cur = System.currentTimeMillis(); + boolean valid = cur - last > 400; + if (valid) { + last = cur; + } + return valid; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/helper/DBOpenHelper.java b/app/src/main/java/com/hiroshi/cimoc/helper/DBOpenHelper.java new file mode 100644 index 00000000..34e2ecf9 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/helper/DBOpenHelper.java @@ -0,0 +1,113 @@ +package com.hiroshi.cimoc.helper; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; + +import com.hiroshi.cimoc.model.ComicDao; +import com.hiroshi.cimoc.model.DaoMaster; +import com.hiroshi.cimoc.model.SourceDao; +import com.hiroshi.cimoc.model.TagDao; +import com.hiroshi.cimoc.model.TagRefDao; +import com.hiroshi.cimoc.model.TaskDao; + +import org.greenrobot.greendao.database.Database; + +/** + * Created by Hiroshi on 2016/8/12. + */ +public class DBOpenHelper extends DaoMaster.OpenHelper { + + public DBOpenHelper(Context context, String name) { + super(context, name); + } + + public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) { + super(context, name, factory); + } + + @Override + public void onCreate(Database db) { + super.onCreate(db); + } + + @Override + public void onUpgrade(Database db, int oldVersion, int newVersion) { + switch (oldVersion) { + case 1: + SourceDao.createTable(db, false); + case 2: + updateHighlight(db); + case 3: + TaskDao.createTable(db, false); + updateDownload(db); + case 4: + case 5: + case 6: + SourceDao.dropTable(db, false); + SourceDao.createTable(db, false); + TagDao.createTable(db, false); + TagRefDao.createTable(db, false); + case 7: + case 8: + updateLocal(db); + case 9: + updateSource(db); + } + } + + private void updateLocal(Database db) { + db.beginTransaction(); + db.execSQL("ALTER TABLE \"COMIC\" RENAME TO \"COMIC2\""); + ComicDao.createTable(db, false); + db.execSQL("INSERT INTO \"COMIC\" (\"_id\", \"SOURCE\", \"CID\", \"TITLE\", \"COVER\", " + + "\"UPDATE\", \"HIGHLIGHT\", \"LOCAL\", \"FAVORITE\", \"HISTORY\", \"DOWNLOAD\", " + + "\"LAST\", \"PAGE\", \"CHAPTER\") SELECT \"_id\", \"SOURCE\", \"CID\", \"TITLE\", " + + "\"COVER\", \"UPDATE\", \"HIGHLIGHT\", 0, \"FAVORITE\", \"HISTORY\", \"DOWNLOAD\", " + + "\"LAST\", \"PAGE\", null FROM \"COMIC2\""); + db.execSQL("DROP TABLE \"COMIC2\""); + db.setTransactionSuccessful(); + db.endTransaction(); + } + + private void updateSource(Database db) { + db.beginTransaction(); + db.execSQL("ALTER TABLE \"SOURCE\" RENAME TO \"SOURCE2\""); + SourceDao.createTable(db, false); + db.execSQL("INSERT INTO \"SOURCE\" (\"_id\", \"TYPE\", \"TITLE\", \"ENABLE\")" + + " SELECT \"_id\", \"TYPE\", \"TITLE\", \"ENABLE\" FROM \"SOURCE2\""); + db.execSQL("DROP TABLE \"SOURCE2\""); + db.execSQL("ALTER TABLE \"COMIC\" ADD COLUMN \"URL\" TEXT"); + db.setTransactionSuccessful(); + db.endTransaction(); + } + + private void updateDownload(Database db) { + db.beginTransaction(); + db.execSQL("ALTER TABLE \"COMIC\" RENAME TO \"COMIC2\""); + ComicDao.createTable(db, false); + db.execSQL("INSERT INTO \"COMIC\" (\"_id\", \"SOURCE\", \"CID\", \"TITLE\", \"COVER\", " + + "\"HIGHLIGHT\", \"UPDATE\", \"FINISH\", \"FAVORITE\", \"HISTORY\", \"DOWNLOAD\", " + + "\"LAST\", \"PAGE\") SELECT \"_id\", \"SOURCE\", \"CID\", \"TITLE\", \"COVER\", " + + "\"HIGHLIGHT\", \"UPDATE\", null, \"FAVORITE\", \"HISTORY\", null, \"LAST\", " + + "\"PAGE\" FROM \"COMIC2\""); + db.execSQL("DROP TABLE \"COMIC2\""); + db.setTransactionSuccessful(); + db.endTransaction(); + } + + private void updateHighlight(Database db) { + db.beginTransaction(); + db.execSQL("ALTER TABLE \"COMIC\" RENAME TO \"COMIC2\""); + ComicDao.createTable(db, false); + db.execSQL("INSERT INTO \"COMIC\" (\"_id\", \"SOURCE\", \"CID\", \"TITLE\", \"COVER\", " + + "\"UPDATE\", \"HIGHLIGHT\", \"FAVORITE\", \"HISTORY\", \"LAST\", \"PAGE\")" + + " SELECT \"_id\", \"SOURCE\", \"CID\", \"TITLE\", \"COVER\", \"UPDATE\", 0, " + + "\"FAVORITE\", \"HISTORY\", \"LAST\", \"PAGE\" FROM \"COMIC2\""); + db.execSQL("DROP TABLE \"COMIC2\""); + db.execSQL("UPDATE \"COMIC\" SET \"HIGHLIGHT\" = 1, \"FAVORITE\" = " + + System.currentTimeMillis() + " WHERE \"FAVORITE\" = " + 0xFFFFFFFFFFFL); + db.setTransactionSuccessful(); + db.endTransaction(); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/helper/UpdateHelper.java b/app/src/main/java/com/hiroshi/cimoc/helper/UpdateHelper.java new file mode 100644 index 00000000..c2d989d6 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/helper/UpdateHelper.java @@ -0,0 +1,93 @@ +package com.hiroshi.cimoc.helper; + +import com.hiroshi.cimoc.BuildConfig; +import com.hiroshi.cimoc.manager.PreferenceManager; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ComicDao; +import com.hiroshi.cimoc.model.DaoSession; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.source.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Hiroshi on 2017/1/18. + */ + +public class UpdateHelper { + + // 1.04.08.008 + private static final int VERSION = BuildConfig.VERSION_CODE; + + public static void update(PreferenceManager manager, final DaoSession session) { + int version = manager.getInt(PreferenceManager.PREF_APP_VERSION, 0); + if (version != VERSION) { + initSource(session); + manager.putInt(PreferenceManager.PREF_APP_VERSION, VERSION); + } + } + + /** + * app: 1.4.8.0 -> 1.4.8.1 + * 删除本地漫画中 download 字段的值 + */ + private static void deleteDownloadFromLocal(final DaoSession session) { + session.runInTx(new Runnable() { + @Override + public void run() { + ComicDao dao = session.getComicDao(); + List list = dao.queryBuilder().where(ComicDao.Properties.Local.eq(true)).list(); + if (!list.isEmpty()) { + for (Comic comic : list) { + comic.setDownload(null); + } + dao.updateInTx(list); + } + } + }); + } + + /** + * 初始化图源 + */ + private static void initSource(DaoSession session) { + List list = new ArrayList<>(); + list.add(IKanman.getDefaultSource()); + list.add(Dmzj.getDefaultSource()); + list.add(HHAAZZ.getDefaultSource()); + list.add(CCTuku.getDefaultSource()); + list.add(U17.getDefaultSource()); + list.add(DM5.getDefaultSource()); + list.add(Webtoon.getDefaultSource()); + list.add(HHSSEE.getDefaultSource()); + list.add(MH57.getDefaultSource()); + list.add(MH50.getDefaultSource()); + list.add(Dmzjv2.getDefaultSource()); + list.add(MangaNel.getDefaultSource()); + list.add(PuFei.getDefaultSource()); + list.add(Cartoonmad.getDefaultSource()); + list.add(Animx2.getDefaultSource()); + list.add(MH517.getDefaultSource()); + list.add(BaiNian.getDefaultSource()); + list.add(MiGu.getDefaultSource()); + list.add(Tencent.getDefaultSource()); + list.add(BuKa.getDefaultSource()); + list.add(EHentai.getDefaultSource()); + list.add(NetEase.getDefaultSource()); + list.add(Hhxxee.getDefaultSource()); + list.add(ChuiXue.getDefaultSource()); + list.add(BaiNian.getDefaultSource()); + list.add(TuHao.getDefaultSource()); + list.add(MangaBZ.getDefaultSource()); + list.add(ManHuaDB.getDefaultSource()); + list.add(Manhuatai.getDefaultSource()); + list.add(GuFeng.getDefaultSource()); + list.add(CCMH.getDefaultSource()); + list.add(Manhuatai.getDefaultSource()); + list.add(MHLove.getDefaultSource()); + list.add(GuFeng.getDefaultSource()); + list.add(YYLS.getDefaultSource()); + session.getSourceDao().insertOrReplaceInTx(list); + } +} diff --git a/app/src/main/java/com/hiroshi/cimoc/manager/ComicManager.java b/app/src/main/java/com/hiroshi/cimoc/manager/ComicManager.java new file mode 100644 index 00000000..2e45bc02 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/manager/ComicManager.java @@ -0,0 +1,201 @@ +package com.hiroshi.cimoc.manager; + +import com.hiroshi.cimoc.component.AppGetter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ComicDao; +import com.hiroshi.cimoc.model.ComicDao.Properties; +import com.hiroshi.cimoc.model.TagRef; +import com.hiroshi.cimoc.model.TagRefDao; + +import org.greenrobot.greendao.query.QueryBuilder; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; + +import rx.Observable; + +/** + * Created by Hiroshi on 2016/7/9. + */ +public class ComicManager { + + private static ComicManager mInstance; + + private ComicDao mComicDao; + + private ComicManager(AppGetter getter) { + mComicDao = getter.getAppInstance().getDaoSession().getComicDao(); + } + + public static ComicManager getInstance(AppGetter getter) { + if (mInstance == null) { + synchronized (ComicManager.class) { + if (mInstance == null) { + mInstance = new ComicManager(getter); + } + } + } + return mInstance; + } + + public void runInTx(Runnable runnable) { + mComicDao.getSession().runInTx(runnable); + } + + public T callInTx(Callable callable) { + return mComicDao.getSession().callInTxNoException(callable); + } + + public List listDownload() { + return mComicDao.queryBuilder() + .where(Properties.Download.isNotNull()) + .list(); + } + + public List listLocal() { + return mComicDao.queryBuilder() + .where(Properties.Local.eq(true)) + .list(); + } + + public Observable> listLocalInRx() { + return mComicDao.queryBuilder() + .where(Properties.Local.eq(true)) + .rx() + .list(); + } + + public Observable> listFavoriteOrHistoryInRx() { + return mComicDao.queryBuilder() + .whereOr(Properties.Favorite.isNotNull(), Properties.History.isNotNull()) + .rx() + .list(); + } + + public List listFavorite() { + return mComicDao.queryBuilder() + .where(Properties.Favorite.isNotNull()) + .list(); + } + + public Observable> listFavoriteInRx() { + return mComicDao.queryBuilder() + .where(Properties.Favorite.isNotNull()) + .orderDesc(Properties.Highlight, Properties.Favorite) + .rx() + .list(); + } + + public Observable> listFinishInRx() { + return mComicDao.queryBuilder() + .where(Properties.Favorite.isNotNull(), Properties.Finish.eq(true)) + .orderDesc(Properties.Highlight, Properties.Favorite) + .rx() + .list(); + } + + public Observable> listContinueInRx() { + return mComicDao.queryBuilder() + .where(Properties.Favorite.isNotNull(), Properties.Finish.notEq(true)) + .orderDesc(Properties.Highlight, Properties.Favorite) + .rx() + .list(); + } + + public Observable> listHistoryInRx() { + return mComicDao.queryBuilder() + .where(Properties.History.isNotNull()) + .orderDesc(Properties.History) + .rx() + .list(); + } + + public Observable> listDownloadInRx() { + return mComicDao.queryBuilder() + .where(Properties.Download.isNotNull()) + .orderDesc(Properties.Download) + .rx() + .list(); + } + + public Observable> listFavoriteByTag(long id) { + QueryBuilder queryBuilder = mComicDao.queryBuilder(); + queryBuilder.join(TagRef.class, TagRefDao.Properties.Cid).where(TagRefDao.Properties.Tid.eq(id)); + return queryBuilder.orderDesc(Properties.Highlight, Properties.Favorite) + .rx() + .list(); + } + + public Observable> listFavoriteNotIn(Collection collections) { + return mComicDao.queryBuilder() + .where(Properties.Favorite.isNotNull(), Properties.Id.notIn(collections)) + .rx() + .list(); + } + + public long countBySource(int type) { + return mComicDao.queryBuilder() + .where(Properties.Source.eq(type), Properties.Favorite.isNotNull()) + .count(); + } + + public Comic load(long id) { + return mComicDao.load(id); + } + + public Comic load(int source, String cid) { + return mComicDao.queryBuilder() + .where(Properties.Source.eq(source), Properties.Cid.eq(cid)) + .unique(); + } + + public Comic loadOrCreate(int source, String cid) { + Comic comic = load(source, cid); + return comic == null ? new Comic(source, cid) : comic; + } + + public Observable loadLast() { + return mComicDao.queryBuilder() + .where(Properties.History.isNotNull()) + .orderDesc(Properties.History) + .limit(1) + .rx() + .unique(); + } + + public void cancelHighlight() { + mComicDao.getDatabase().execSQL("UPDATE \"COMIC\" SET \"HIGHLIGHT\" = 0 WHERE \"HIGHLIGHT\" = 1"); + } + + public void updateOrInsert(Comic comic) { + if (comic.getId() == null) { + insert(comic); + } else { + update(comic); + } + } + + public void update(Comic comic) { + mComicDao.update(comic); + } + + public void updateOrDelete(Comic comic) { + if (comic.getFavorite() == null && comic.getHistory() == null && comic.getDownload() == null) { + mComicDao.delete(comic); + comic.setId(null); + } else { + update(comic); + } + } + + public void deleteByKey(long key) { + mComicDao.deleteByKey(key); + } + + public void insert(Comic comic) { + long id = mComicDao.insert(comic); + comic.setId(id); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/manager/PreferenceManager.java b/app/src/main/java/com/hiroshi/cimoc/manager/PreferenceManager.java new file mode 100644 index 00000000..d6418cf3 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/manager/PreferenceManager.java @@ -0,0 +1,190 @@ +package com.hiroshi.cimoc.manager; + +import android.content.Context; +import android.content.SharedPreferences; + +import java.util.Map; + +/** + * Created by Hiroshi on 2016/8/4. + */ +public class PreferenceManager { + + public static final int READER_MODE_PAGE = 0; + public static final int READER_MODE_STREAM = 1; + + public static final int READER_TURN_LTR = 0; + public static final int READER_TURN_RTL = 1; + public static final int READER_TURN_ATB = 2; + + public static final int READER_ORIENTATION_PORTRAIT = 0; + public static final int READER_ORIENTATION_LANDSCAPE = 1; + public static final int READER_ORIENTATION_AUTO = 2; + + public static final int HOME_FAVORITE = 0; + public static final int HOME_SOURCE = 1; + public static final int HOME_TAG = 2; + public static final int HOME_HISTORY = 3; + public static final int HOME_DOWNLOAD = 4; + public static final int HOME_LOCAL = 5; + + public static final String PREF_APP_VERSION = "pref_app_version"; + + public static final String PREF_MAIN_NOTICE = "pref_main_notice"; + public static final String PREF_MAIN_NOTICE_LAST = "pref_main_notice_last"; + + public static final String PREF_READER_MODE = "pref_reader_mode"; + public static final String PREF_READER_KEEP_BRIGHT = "pref_reader_keep_on"; + public static final String PREF_READER_HIDE_INFO = "pref_reader_hide"; + public static final String PREF_READER_HIDE_NAV = "pref_reader_hide_nav"; + public static final String PREF_READER_BAN_DOUBLE_CLICK = "pref_reader_ban_double_click"; + public static final String PREF_READER_PAGING = "pref_reader_paging"; + public static final String PREF_READER_PAGING_REVERSE = "pref_reader_paging_reverse"; + public static final String PREF_READER_WHITE_EDGE = "pref_reader_white_edge"; + public static final String PREF_READER_WHITE_BACKGROUND = "pref_reader_white_background"; + public static final String PREF_READER_SCALE_FACTOR = "pref_reader_scale_factor"; + public static final String PREF_READER_CONTROLLER_TRIG_THRESHOLD = "pref_reader_controller_trig_threshold"; + + public static final String PREF_READER_PAGE_TURN = "pref_reader_page_turn"; + public static final String PREF_READER_PAGE_ORIENTATION = "pref_reader_page_orientation"; + public static final String PREF_READER_PAGE_CLICK_LEFT = "pref_reader_page_click_left"; + public static final String PREF_READER_PAGE_CLICK_TOP = "pref_reader_page_click_top"; + public static final String PREF_READER_PAGE_CLICK_MIDDLE = "pref_reader_page_click_middle"; + public static final String PREF_READER_PAGE_CLICK_BOTTOM = "pref_reader_page_click_bottom"; + public static final String PREF_READER_PAGE_CLICK_RIGHT = "pref_reader_page_click_right"; + public static final String PREF_READER_PAGE_CLICK_UP = "pref_reader_page_click_up"; + public static final String PREF_READER_PAGE_CLICK_DOWN = "pref_reader_page_click_down"; + public static final String PREF_READER_PAGE_JOY_LT = "pref_reader_page_joy_lt"; + public static final String PREF_READER_PAGE_JOY_RT = "pref_reader_page_joy_rt"; + public static final String PREF_READER_PAGE_JOY_A = "pref_reader_page_joy_a"; + public static final String PREF_READER_PAGE_JOY_B = "pref_reader_page_joy_b"; + public static final String PREF_READER_PAGE_JOY_X = "pref_reader_page_joy_x"; + public static final String PREF_READER_PAGE_JOY_Y = "pref_reader_page_joy_y"; + public static final String PREF_READER_PAGE_JOY_LEFT = "pref_reader_page_joy_left"; + public static final String PREF_READER_PAGE_JOY_RIGHT = "pref_reader_page_joy_right"; + public static final String PREF_READER_PAGE_JOY_UP = "pref_reader_page_joy_up"; + public static final String PREF_READER_PAGE_JOY_DOWN = "pref_reader_page_joy_down"; + public static final String PREF_READER_PAGE_LONG_CLICK_LEFT = "pref_reader_page_long_click_left"; + public static final String PREF_READER_PAGE_LONG_CLICK_TOP = "pref_reader_page_long_click_top"; + public static final String PREF_READER_PAGE_LONG_CLICK_MIDDLE = "pref_reader_page_long_click_middle"; + public static final String PREF_READER_PAGE_LONG_CLICK_BOTTOM = "pref_reader_page_long_click_bottom"; + public static final String PREF_READER_PAGE_LONG_CLICK_RIGHT = "pref_reader_page_long_click_right"; + public static final String PREF_READER_PAGE_LOAD_PREV = "pref_reader_page_load_prev"; + public static final String PREF_READER_PAGE_LOAD_NEXT = "pref_reader_page_load_next"; + public static final String PREF_READER_PAGE_TRIGGER = "pref_reader_page_trigger"; + public static final String PREF_READER_PAGE_BAN_TURN = "pref_reader_page_ban_turn"; + public static final String PREF_READER_PAGE_QUICK_TURN = "pref_reader_page_quick_turn"; + + public static final String PREF_READER_STREAM_TURN = "pref_reader_stream_turn"; + public static final String PREF_READER_STREAM_ORIENTATION = "pref_reader_stream_orientation"; + public static final String PREF_READER_STREAM_CLICK_LEFT = "pref_reader_stream_click_left"; + public static final String PREF_READER_STREAM_CLICK_TOP = "pref_reader_stream_click_top"; + public static final String PREF_READER_STREAM_CLICK_MIDDLE = "pref_reader_stream_click_middle"; + public static final String PREF_READER_STREAM_CLICK_BOTTOM = "pref_reader_stream_click_bottom"; + public static final String PREF_READER_STREAM_CLICK_RIGHT = "pref_reader_stream_click_right"; + public static final String PREF_READER_STREAM_CLICK_UP = "pref_reader_stream_click_up"; + public static final String PREF_READER_STREAM_CLICK_DOWN = "pref_reader_stream_click_down"; + public static final String PREF_READER_STREAM_JOY_LT = "pref_reader_stream_joy_lt"; + public static final String PREF_READER_STREAM_JOY_RT = "pref_reader_stream_joy_rt"; + public static final String PREF_READER_STREAM_JOY_A = "pref_reader_stream_joy_a"; + public static final String PREF_READER_STREAM_JOY_B = "pref_reader_stream_joy_b"; + public static final String PREF_READER_STREAM_JOY_X = "pref_reader_stream_joy_x"; + public static final String PREF_READER_STREAM_JOY_Y = "pref_reader_stream_joy_y"; + public static final String PREF_READER_STREAM_JOY_LEFT = "pref_reader_stream_joy_left"; + public static final String PREF_READER_STREAM_JOY_RIGHT = "pref_reader_stream_joy_right"; + public static final String PREF_READER_STREAM_JOY_UP = "pref_reader_stream_joy_up"; + public static final String PREF_READER_STREAM_JOY_DOWN = "pref_reader_stream_joy_down"; + public static final String PREF_READER_STREAM_LONG_CLICK_LEFT = "pref_reader_stream_long_click_left"; + public static final String PREF_READER_STREAM_LONG_CLICK_TOP = "pref_reader_stream_long_click_top"; + public static final String PREF_READER_STREAM_LONG_CLICK_MIDDLE = "pref_reader_stream_long_click_middle"; + public static final String PREF_READER_STREAM_LONG_CLICK_BOTTOM = "pref_reader_stream_long_click_bottom"; + public static final String PREF_READER_STREAM_LONG_CLICK_RIGHT = "pref_reader_stream_long_click_right"; + public static final String PREF_READER_STREAM_LOAD_PREV = "pref_reader_stream_load_prev"; + public static final String PREF_READER_STREAM_LOAD_NEXT = "pref_reader_stream_load_next"; + public static final String PREF_READER_STREAM_INTERVAL = "pref_reader_stream_interval"; + + public static final String PREF_NIGHT = "pref_night"; + + public static final String PREF_UPDATE_APP_AUTO = "pref_update_app_auto"; + + public static final String PREF_UPDATE_CURRENT_URL = "pref_update_current_url"; + + public static final String PREF_OTHER_CHECK_UPDATE = "pref_other_check_update"; + public static final String PREF_OTHER_CONNECT_ONLY_WIFI = "pref_other_connect_only_wifi"; + public static final String PREF_OTHER_LOADCOVER_ONLY_WIFI = "pref_other_loadcover_only_wifi"; + public static final String PREF_OTHER_FIREBASE_EVENT = "pref_other_firebase_event"; + public static final String PREF_OTHER_CHECK_UPDATE_LAST = "pref_other_check_update_last"; + public static final String PREF_OTHER_STORAGE = "pref_other_storage"; + public static final String PREF_OTHER_THEME = "pref_other_theme"; + public static final String PREF_OTHER_LAUNCH = "pref_other_launch"; + public static final String PREF_OTHER_NIGHT_ALPHA = "pref_other_night_alpha"; + public static final String PREF_OTHER_SHOW_TOPBAR = "pref_other_show_topbar"; + + public static final String PREF_DOWNLOAD_THREAD = "pref_download_thread"; + + public static final String PREF_BACKUP_SAVE_COMIC = "pref_backup_save_favorite"; + public static final String PREF_BACKUP_SAVE_COMIC_COUNT = "pref_backup_save_favorite_count"; + + public static final String PREF_SEARCH_AUTO_COMPLETE = "pref_search_auto_complete"; + + public static final String PREF_CHAPTER_BUTTON_MODE = "pref_chapter_button_mode"; + public static final String PREF_CHAPTER_ASCEND_MODE = "pref_chapter_ascend_mode"; + public static final String PREFERENCES_USER_TOCKEN = "user_tocken"; + public static final String PREFERENCES_USER_NAME = "user_name"; + public static final String PREFERENCES_USER_EMAIL = "user_email"; + public static final String PREFERENCES_USER_ID = "user_id"; + private static final String PREFERENCES_NAME = "cimoc_preferences"; + private SharedPreferences mSharedPreferences; + + public PreferenceManager(Context context) { + mSharedPreferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); + } + + public String getString(String key) { + return getString(key, null); + } + + public String getString(String key, String defValue) { + return mSharedPreferences.getString(key, defValue); + } + + public boolean getBoolean(String key, boolean defValue) { + return mSharedPreferences.getBoolean(key, defValue); + } + + public int getInt(String key, int defValue) { + return mSharedPreferences.getInt(key, defValue); + } + + public long getLong(String key, long defValue) { + return mSharedPreferences.getLong(key, defValue); + } + + public void putString(String key, String value) { + mSharedPreferences.edit().putString(key, value).apply(); + } + + public void putBoolean(String key, boolean value) { + mSharedPreferences.edit().putBoolean(key, value).apply(); + } + + public void putInt(String key, int value) { + mSharedPreferences.edit().putInt(key, value).apply(); + } + + public void putLong(String key, long value) { + mSharedPreferences.edit().putLong(key, value).apply(); + } + + public Map getAll() { + return mSharedPreferences.getAll(); + } + + public void putObject(String key, Object value) { + if (value instanceof Boolean) mSharedPreferences.edit().putBoolean(key, (Boolean) value).apply(); + else if (value instanceof Float) mSharedPreferences.edit().putFloat(key, (Float) value).apply(); + else if (value instanceof Integer) mSharedPreferences.edit().putInt(key, (Integer) value).apply(); + else if (value instanceof Long) mSharedPreferences.edit().putLong(key, (Long) value).apply(); + else if (value instanceof String) mSharedPreferences.edit().putString(key, ((String) value)).apply(); + } +} diff --git a/app/src/main/java/com/hiroshi/cimoc/manager/SourceManager.java b/app/src/main/java/com/hiroshi/cimoc/manager/SourceManager.java new file mode 100644 index 00000000..fbe944bc --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/manager/SourceManager.java @@ -0,0 +1,209 @@ +package com.hiroshi.cimoc.manager; + +import android.util.SparseArray; + +import com.hiroshi.cimoc.component.AppGetter; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.model.SourceDao; +import com.hiroshi.cimoc.model.SourceDao.Properties; +import com.hiroshi.cimoc.parser.Parser; +import com.hiroshi.cimoc.source.*; + +import java.util.List; + +import okhttp3.Headers; +import rx.Observable; + +/** + * Created by Hiroshi on 2016/8/11. + */ +public class SourceManager { + + private static SourceManager mInstance; + + private SourceDao mSourceDao; + private SparseArray mParserArray = new SparseArray<>(); + + private SourceManager(AppGetter getter) { + mSourceDao = getter.getAppInstance().getDaoSession().getSourceDao(); + } + + public static SourceManager getInstance(AppGetter getter) { + if (mInstance == null) { + synchronized (SourceManager.class) { + if (mInstance == null) { + mInstance = new SourceManager(getter); + } + } + } + return mInstance; + } + + public Observable> list() { + return mSourceDao.queryBuilder() + .orderAsc(Properties.Type) + .rx() + .list(); + } + + public Observable> listEnableInRx() { + return mSourceDao.queryBuilder() + .where(Properties.Enable.eq(true)) + .orderAsc(Properties.Type) + .rx() + .list(); + } + + public List listEnable() { + return mSourceDao.queryBuilder() + .where(Properties.Enable.eq(true)) + .orderAsc(Properties.Type) + .list(); + } + + public Source load(int type) { + return mSourceDao.queryBuilder() + .where(Properties.Type.eq(type)) + .unique(); + } + + public long insert(Source source) { + return mSourceDao.insert(source); + } + + public void update(Source source) { + mSourceDao.update(source); + } + + public Parser getParser(int type) { + Parser parser = mParserArray.get(type); + if (parser == null) { + Source source = load(type); + switch (type) { + case IKanman.TYPE: + parser = new IKanman(source); + break; + case Dmzj.TYPE: + parser = new Dmzj(source); + break; + case HHAAZZ.TYPE: + parser = new HHAAZZ(source); + break; + case CCTuku.TYPE: + parser = new CCTuku(source); + break; + case U17.TYPE: + parser = new U17(source); + break; + case DM5.TYPE: + parser = new DM5(source); + break; + case Webtoon.TYPE: + parser = new Webtoon(source); + break; + case HHSSEE.TYPE: + parser = new HHSSEE(source); + break; + case MH57.TYPE: + parser = new MH57(source); + break; + case MH50.TYPE: + parser = new MH50(source); + break; + case Dmzjv2.TYPE: + parser = new Dmzjv2(source); + break; + case Locality.TYPE: + parser = new Locality(); + break; + case MangaNel.TYPE: + parser = new MangaNel(source); + break; + + //feilong + case PuFei.TYPE: + parser = new PuFei(source); + break; + case Tencent.TYPE: + parser = new Tencent(source); + break; + case BuKa.TYPE: + parser = new BuKa(source); + break; + case EHentai.TYPE: + parser = new EHentai(source); + break; + case NetEase.TYPE: + parser = new NetEase(source); + break; + case Hhxxee.TYPE: + parser = new Hhxxee(source); + break; + case Cartoonmad.TYPE: + parser = new Cartoonmad(source); + break; + case Animx2.TYPE: + parser = new Animx2(source); + break; + case MH517.TYPE: + parser = new MH517(source); + break; + case MiGu.TYPE: + parser = new MiGu(source); + break; + case BaiNian.TYPE: + parser = new BaiNian(source); + break; + case ChuiXue.TYPE: + parser = new ChuiXue(source); + break; + case TuHao.TYPE: + parser = new TuHao(source); + break; + case ManHuaDB.TYPE: + parser = new ManHuaDB(source); + break; + case Manhuatai.TYPE: + parser = new Manhuatai(source); + break; + case GuFeng.TYPE: + parser = new GuFeng(source); + break; + case CCMH.TYPE: + parser = new CCMH(source); + break; + case MHLove.TYPE: + parser = new MHLove(source); + break; + case YYLS.TYPE: + parser = new YYLS(source); + break; + case MangaBZ.TYPE: + parser = new MangaBZ(source); + break; + + default: + parser = new Null(); + break; + } + mParserArray.put(type, parser); + } + return parser; + } + + public class TitleGetter { + + public String getTitle(int type) { + return getParser(type).getTitle(); + } + + } + + public class HeaderGetter { + + public Headers getHeader(int type) { + return getParser(type).getHeader(); + } + + } +} diff --git a/app/src/main/java/com/hiroshi/cimoc/manager/TagManager.java b/app/src/main/java/com/hiroshi/cimoc/manager/TagManager.java new file mode 100644 index 00000000..af79edaa --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/manager/TagManager.java @@ -0,0 +1,69 @@ +package com.hiroshi.cimoc.manager; + +import com.hiroshi.cimoc.component.AppGetter; +import com.hiroshi.cimoc.model.Tag; +import com.hiroshi.cimoc.model.TagDao; + +import java.util.List; + +import rx.Observable; + +/** + * Created by Hiroshi on 2016/10/10. + */ + +public class TagManager { + + public static final long TAG_CONTINUE = -101; + public static final long TAG_FINISH = -100; + + private static TagManager mInstance; + + private TagDao mTagDao; + + private TagManager(AppGetter getter) { + mTagDao = getter.getAppInstance().getDaoSession().getTagDao(); + } + + public static TagManager getInstance(AppGetter getter) { + if (mInstance == null) { + synchronized (TagManager.class) { + if (mInstance == null) { + mInstance = new TagManager(getter); + } + } + } + return mInstance; + } + + public List list() { + return mTagDao.queryBuilder().list(); + } + + public Observable> listInRx() { + return mTagDao.queryBuilder() + .rx() + .list(); + } + + public Tag load(String title) { + return mTagDao.queryBuilder() + .where(TagDao.Properties.Title.eq(title)) + .limit(1) + .unique(); + } + + public void insert(Tag tag) { + long id = mTagDao.insert(tag); + tag.setId(id); + } + + public void update(Tag tag) { + mTagDao.update(tag); + } + + public void delete(Tag entity) { + mTagDao.delete(entity); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/manager/TagRefManager.java b/app/src/main/java/com/hiroshi/cimoc/manager/TagRefManager.java new file mode 100644 index 00000000..ecc27e87 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/manager/TagRefManager.java @@ -0,0 +1,95 @@ +package com.hiroshi.cimoc.manager; + +import com.hiroshi.cimoc.component.AppGetter; +import com.hiroshi.cimoc.model.TagRef; +import com.hiroshi.cimoc.model.TagRefDao; + +import java.util.List; + +import rx.Observable; + +/** + * Created by Hiroshi on 2017/1/16. + */ + +public class TagRefManager { + + private static TagRefManager mInstance; + + private TagRefDao mRefDao; + + private TagRefManager(AppGetter getter) { + mRefDao = getter.getAppInstance().getDaoSession().getTagRefDao(); + } + + public static TagRefManager getInstance(AppGetter getter) { + if (mInstance == null) { + synchronized (TagRefManager.class) { + if (mInstance == null) { + mInstance = new TagRefManager(getter); + } + } + } + return mInstance; + } + + public Observable runInRx(Runnable runnable) { + return mRefDao.getSession().rxTx().run(runnable); + } + + public void runInTx(Runnable runnable) { + mRefDao.getSession().runInTx(runnable); + } + + public List listByTag(long tid) { + return mRefDao.queryBuilder() + .where(TagRefDao.Properties.Tid.eq(tid)) + .list(); + } + + public List listByComic(long cid) { + return mRefDao.queryBuilder() + .where(TagRefDao.Properties.Cid.eq(cid)) + .list(); + } + + public TagRef load(long tid, long cid) { + return mRefDao.queryBuilder() + .where(TagRefDao.Properties.Tid.eq(tid), TagRefDao.Properties.Cid.eq(cid)) + .unique(); + } + + public long insert(TagRef ref) { + return mRefDao.insert(ref); + } + + public void insert(Iterable entities) { + mRefDao.insertInTx(entities); + } + + public void insertInTx(Iterable entities) { + mRefDao.insertInTx(entities); + } + + public void deleteByTag(long tid) { + mRefDao.queryBuilder() + .where(TagRefDao.Properties.Tid.eq(tid)) + .buildDelete() + .executeDeleteWithoutDetachingEntities(); + } + + public void deleteByComic(long cid) { + mRefDao.queryBuilder() + .where(TagRefDao.Properties.Cid.eq(cid)) + .buildDelete() + .executeDeleteWithoutDetachingEntities(); + } + + public void delete(long tid, long cid) { + mRefDao.queryBuilder() + .where(TagRefDao.Properties.Tid.eq(tid), TagRefDao.Properties.Cid.eq(cid)) + .buildDelete() + .executeDeleteWithoutDetachingEntities(); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/manager/TaskManager.java b/app/src/main/java/com/hiroshi/cimoc/manager/TaskManager.java new file mode 100644 index 00000000..4907ef1a --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/manager/TaskManager.java @@ -0,0 +1,114 @@ +package com.hiroshi.cimoc.manager; + +import com.hiroshi.cimoc.component.AppGetter; +import com.hiroshi.cimoc.model.Task; +import com.hiroshi.cimoc.model.TaskDao; +import com.hiroshi.cimoc.model.TaskDao.Properties; + +import org.greenrobot.greendao.query.QueryBuilder; + +import java.util.List; + +import rx.Observable; + +/** + * Created by Hiroshi on 2016/9/4. + */ +public class TaskManager { + + private static TaskManager mInstance; + + private TaskDao mTaskDao; + + private TaskManager(AppGetter getter) { + mTaskDao = getter.getAppInstance().getDaoSession().getTaskDao(); + } + + public static TaskManager getInstance(AppGetter getter) { + if (mInstance == null) { + synchronized (TaskManager.class) { + if (mInstance == null) { + mInstance = new TaskManager(getter); + } + } + } + return mInstance; + } + + public List list() { + return mTaskDao.queryBuilder().list(); + } + + public List listValid() { + return mTaskDao.queryBuilder() + .where(Properties.Max.notEq(0)) + .list(); + } + + public List list(long key) { + return mTaskDao.queryBuilder() + .where(Properties.Key.eq(key)) + .list(); + } + + public Observable> listInRx(long key) { + return mTaskDao.queryBuilder() + .where(Properties.Key.eq(key)) + .rx() + .list(); + } + + public Observable> listInRx() { + return mTaskDao.queryBuilder() + .rx() + .list(); + } + + public void insert(Task task) { + long id = mTaskDao.insert(task); + task.setId(id); + } + + public void insertInTx(Iterable entities) { + mTaskDao.insertInTx(entities); + } + + public void update(Task task) { + mTaskDao.update(task); + } + + public void delete(Task task) { + mTaskDao.delete(task); + } + + public void delete(long id) { + mTaskDao.deleteByKey(id); + } + + public void deleteInTx(Iterable entities) { + mTaskDao.deleteInTx(entities); + } + + public void deleteByComicId(long id) { + mTaskDao.queryBuilder() + .where(Properties.Key.eq(id)) + .buildDelete() + .executeDeleteWithoutDetachingEntities(); + } + + public void insertIfNotExist(final Iterable entities) { + mTaskDao.getSession().runInTx(new Runnable() { + @Override + public void run() { + for (Task task : entities) { + QueryBuilder builder = mTaskDao.queryBuilder() + .where(Properties.Key.eq(task.getKey()), Properties.Path.eq(task.getPath())); + if (builder.unique() == null) { + mTaskDao.insert(task); + } + } + } + }); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/misc/ActivityLifecycle.java b/app/src/main/java/com/hiroshi/cimoc/misc/ActivityLifecycle.java new file mode 100644 index 00000000..41768258 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/misc/ActivityLifecycle.java @@ -0,0 +1,59 @@ +package com.hiroshi.cimoc.misc; + +import android.app.Activity; +import android.app.Application; +import android.os.Bundle; + +import java.util.LinkedList; +import java.util.List; + +/** + * Created by Hiroshi on 2018/2/13. + */ + +public class ActivityLifecycle implements Application.ActivityLifecycleCallbacks { + + private List mActivityList; + + public ActivityLifecycle() { + mActivityList = new LinkedList<>(); + } + + public void clear() { + for (Activity activity : mActivityList) { + activity.finish(); + } + mActivityList.clear(); + } + + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + mActivityList.add(activity); + } + + @Override + public void onActivityStarted(Activity activity) { + } + + @Override + public void onActivityResumed(Activity activity) { + } + + @Override + public void onActivityPaused(Activity activity) { + } + + @Override + public void onActivityStopped(Activity activity) { + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + } + + @Override + public void onActivityDestroyed(Activity activity) { + mActivityList.remove(activity); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/misc/NotificationWrapper.java b/app/src/main/java/com/hiroshi/cimoc/misc/NotificationWrapper.java new file mode 100644 index 00000000..2d81fe59 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/misc/NotificationWrapper.java @@ -0,0 +1,52 @@ +package com.hiroshi.cimoc.misc; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import androidx.annotation.DrawableRes; +import androidx.core.app.NotificationCompat; + +import com.hiroshi.cimoc.R; + +/** + * Created by Hiroshi on 2018/2/11. + */ + +public class NotificationWrapper { + + private NotificationManager mManager; + private NotificationCompat.Builder mBuilder; + private int mId; + + public NotificationWrapper(Context context, String id, @DrawableRes int icon, boolean ongoing) { + String title = context.getString(R.string.app_name); + mManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mManager.createNotificationChannel(new NotificationChannel(id, id, NotificationManager.IMPORTANCE_MIN)); + } + mBuilder = new NotificationCompat.Builder(context, id); + mBuilder.setContentTitle(title).setSmallIcon(icon).setOngoing(ongoing); + mId = id.hashCode(); + } + + public void post(int progress, int max) { + mBuilder.setProgress(max, progress, false); + mManager.notify(mId, mBuilder.build()); + } + + public void post(String content, int progress, int max) { + mBuilder.setContentText(content).setTicker(content); + post(progress, max); + } + + public void post(String content, boolean ongoing) { + mBuilder.setOngoing(ongoing); + post(content, 0, 0); + } + + public void cancel() { + mManager.cancel(mId); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/misc/Switcher.java b/app/src/main/java/com/hiroshi/cimoc/misc/Switcher.java new file mode 100644 index 00000000..7b57d5a6 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/misc/Switcher.java @@ -0,0 +1,37 @@ +package com.hiroshi.cimoc.misc; + +/** + * Created by Hiroshi on 2017/9/29. + */ + +public class Switcher { + + private T element; + private boolean enable; + + public Switcher(T element, boolean enable) { + this.element = element; + this.enable = enable; + } + + public T getElement() { + return element; + } + + public void setElement(T element) { + this.element = element; + } + + public boolean isEnable() { + return enable; + } + + public void setEnable(boolean enable) { + this.enable = enable; + } + + public void switchEnable() { + this.enable = !this.enable; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/model/Chapter.java b/app/src/main/java/com/hiroshi/cimoc/model/Chapter.java index c4f5b466..67085959 100644 --- a/app/src/main/java/com/hiroshi/cimoc/model/Chapter.java +++ b/app/src/main/java/com/hiroshi/cimoc/model/Chapter.java @@ -1,16 +1,50 @@ package com.hiroshi.cimoc.model; +import android.os.Parcel; +import android.os.Parcelable; + /** * Created by Hiroshi on 2016/7/2. */ -public class Chapter { +public class Chapter implements Parcelable { - String title; - String path; + public final static Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Chapter createFromParcel(Parcel source) { + return new Chapter(source); + } - public Chapter(String title, String path) { + @Override + public Chapter[] newArray(int size) { + return new Chapter[size]; + } + }; + private String title; + private String path; + private int count; + private boolean complete; + private boolean download; + private long tid; + + public Chapter(String title, String path, int count, boolean complete, boolean download, long tid) { this.title = title; this.path = path; + this.count = count; + this.complete = complete; + this.download = download; + this.tid = tid; + } + + public Chapter(String title, String path, long tid) { + this(title, path, 0, false, false, tid); + } + + public Chapter(String title, String path) { + this(title, path, 0, false, false, -1); + } + + public Chapter(Parcel source) { + this(source.readString(), source.readString(), source.readInt(), source.readByte() == 1, source.readByte() == 1, source.readLong()); } public String getTitle() { @@ -21,4 +55,61 @@ public String getPath() { return path; } + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public boolean isComplete() { + return complete; + } + + public void setComplete(boolean complete) { + this.complete = complete; + } + + public boolean isDownload() { + return download; + } + + public void setDownload(boolean download) { + this.download = download; + } + + public long getTid() { + return tid; + } + + public void setTid(long tid) { + this.tid = tid; + } + + @Override + public boolean equals(Object o) { + return o instanceof Chapter && ((Chapter) o).path.equals(path); + } + + @Override + public int hashCode() { + return path.hashCode(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(title); + dest.writeString(path); + dest.writeInt(count); + dest.writeByte((byte) (complete ? 1 : 0)); + dest.writeByte((byte) (download ? 1 : 0)); + dest.writeLong(tid); + } + } diff --git a/app/src/main/java/com/hiroshi/cimoc/model/Comic.java b/app/src/main/java/com/hiroshi/cimoc/model/Comic.java index 3dd8fdff..92428a56 100644 --- a/app/src/main/java/com/hiroshi/cimoc/model/Comic.java +++ b/app/src/main/java/com/hiroshi/cimoc/model/Comic.java @@ -1,10 +1,10 @@ package com.hiroshi.cimoc.model; import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; import org.greenrobot.greendao.annotation.Id; import org.greenrobot.greendao.annotation.NotNull; import org.greenrobot.greendao.annotation.Transient; -import org.greenrobot.greendao.annotation.Generated; /** * Created by Hiroshi on 2016/7/20. @@ -12,68 +12,98 @@ @Entity public class Comic { - @Id(autoincrement = true) private Long id; - @NotNull private Integer source; - @NotNull private String cid; - @NotNull private String title; - @NotNull private String cover; - @NotNull private String update; + @Id(autoincrement = true) + private Long id; + @NotNull + private int source; + @NotNull + private String cid; + @NotNull + private String title; + @NotNull + private String cover; + @NotNull + private boolean highlight; + @NotNull + private boolean local; + private String update; + private Boolean finish; private Long favorite; private Long history; + private Long download; private String last; private Integer page; + private String chapter; + private String url; + + @Transient + private String intro; + @Transient + private String author; + + public Comic(int source, String cid, String title, String cover, String update, String author) { + this(null, source, cid, title, cover == null ? "" : cover, false, false, update, + null, null, null, null, null, null, null, null); + this.author = author; + } + + public Comic(int source, String cid) { + this.source = source; + this.cid = cid; + } - @Transient private String intro; - @Transient private String author; - @Transient private Boolean status; + public Comic(int source, String cid, String title, String cover, long download) { + this(null, source, cid, title, cover == null ? "" : cover, false, false, null, + null, null, null, download, null, null, null, null); + } - @Generated(hash = 1262295933) - public Comic(Long id, @NotNull Integer source, @NotNull String cid, - @NotNull String title, @NotNull String cover, @NotNull String update, - Long favorite, Long history, String last, Integer page) { + @Generated(hash = 2020487280) + public Comic(Long id, int source, @NotNull String cid, @NotNull String title, @NotNull String cover, boolean highlight, + boolean local, String update, Boolean finish, Long favorite, Long history, Long download, String last, Integer page, + String chapter, String url) { this.id = id; this.source = source; this.cid = cid; this.title = title; this.cover = cover; + this.highlight = highlight; + this.local = local; this.update = update; + this.finish = finish; this.favorite = favorite; this.history = history; + this.download = download; this.last = last; this.page = page; + this.chapter = chapter; + this.url = url; } @Generated(hash = 1347984162) public Comic() { } - public Comic(Integer source, String cid, String title, String cover, String update, String author, Boolean status) { - this.source = source; - this.cid = cid; - this.title = title; - this.cover = cover; - this.update = update; - this.author = author; - this.status = status; - } - - public Comic(Integer source, String cid) { - this.source = source; - this.cid = cid; - } - @Override public boolean equals(Object o) { return o instanceof Comic && ((Comic) o).id.equals(id); } - public void setInfo(String title, String cover, String update, String intro, String author, boolean status) { - this.title = title; - this.cover = cover; - this.update = update; + public void setInfo(String title, String cover, String update, String intro, String author, boolean finish) { + if (title != null) { + this.title = title; + } + if (cover != null) { + this.cover = cover; + } + if (update != null) { + this.update = update; + } this.intro = intro; - this.author = author; - this.status = status; + if (author != null) { + this.author = author; + } + this.finish = finish; + this.highlight = false; } public String getIntro() { @@ -92,14 +122,6 @@ public void setAuthor(String author) { this.author = author; } - public Boolean getStatus() { - return this.status; - } - - public void setStatus(Boolean status) { - this.status = status; - } - public Integer getPage() { return this.page; } @@ -164,11 +186,11 @@ public void setCid(String cid) { this.cid = cid; } - public Integer getSource() { + public int getSource() { return this.source; } - public void setSource(Integer source) { + public void setSource(int source) { this.source = source; } @@ -180,4 +202,51 @@ public void setId(Long id) { this.id = id; } + public boolean getHighlight() { + return this.highlight; + } + + public void setHighlight(boolean highlight) { + this.highlight = highlight; + } + + public Long getDownload() { + return this.download; + } + + public void setDownload(Long download) { + this.download = download; + } + + public Boolean getFinish() { + return this.finish; + } + + public void setFinish(Boolean finish) { + this.finish = finish; + } + + public boolean getLocal() { + return this.local; + } + + public void setLocal(boolean local) { + this.local = local; + } + + public String getChapter() { + return this.chapter; + } + + public void setChapter(String chapter) { + this.chapter = chapter; + } + + public String getUrl() { + return this.url; + } + + public void setUrl(String url) { + this.url = url; + } } diff --git a/app/src/main/java/com/hiroshi/cimoc/model/EventMessage.java b/app/src/main/java/com/hiroshi/cimoc/model/EventMessage.java deleted file mode 100644 index 350f8062..00000000 --- a/app/src/main/java/com/hiroshi/cimoc/model/EventMessage.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.hiroshi.cimoc.model; - -/** - * Created by Hiroshi on 2016/7/2. - */ -public class EventMessage { - - public static final int SEARCH_SUCCESS = 1; - public static final int SEARCH_FAIL = 2; - public static final int LOAD_COMIC_SUCCESS = 3; - public static final int LOAD_COMIC_FAIL = 4; - public static final int PARSE_PIC_SUCCESS = 5; - public static final int PARSE_PIC_FAIL = 6; - public static final int NETWORK_ERROR = 7; - public static final int FAVORITE_COMIC = 8; - public static final int UN_FAVORITE_COMIC = 9; - public static final int HISTORY_COMIC = 10; - public static final int AFTER_READ = 11; - public static final int DELETE_HISTORY = 12; - public static final int RESTORE_FAVORITE = 13; - - private int type; - private Object data; - - public EventMessage(int type, Object data) { - this.type = type; - this.data = data; - } - - public int getType() { - return type; - } - - public Object getData() { - return data; - } - -} diff --git a/app/src/main/java/com/hiroshi/cimoc/model/ImageUrl.java b/app/src/main/java/com/hiroshi/cimoc/model/ImageUrl.java new file mode 100644 index 00000000..7ed56022 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/model/ImageUrl.java @@ -0,0 +1,148 @@ +package com.hiroshi.cimoc.model; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by Hiroshi on 2016/8/20. + */ +public class ImageUrl { + + public static final int STATE_NULL = 0; + public static final int STATE_PAGE_1 = 1; + //public static final int STATE_PAGE_Finish = 2; + private static AtomicInteger count = new AtomicInteger(0); + private int id; // 唯一标识 + private int num; // 章节的第几页 + private String[] urls; + private String chapter; // 所属章节 + private int state; // 切图时表示状态编号 比如长图可以切为多张方便加载 + private int height; // 图片高度 + private int width; // 图片宽度 + private boolean lazy; // 懒加载 + private boolean loading; // 正在懒加载 + private boolean success; // 图片显示成功 + private boolean download; // 下载的图片 + + public ImageUrl(int num, String url, boolean lazy) { + this(num, new String[]{url}, lazy); + } + + public ImageUrl(int num, String[] urls, boolean lazy) { + this(num, urls, STATE_NULL, lazy); + } + + public ImageUrl(int num, String[] urls, int state, boolean lazy) { + this(num, urls, null, state, lazy); + } + + public ImageUrl(int num, String[] urls, String chapter, int state, boolean lazy) { + this.id = count.getAndAdd(1); + this.num = num; + this.urls = urls; + this.chapter = chapter; + this.state = state; + this.height = 0; + this.width = 0; + this.lazy = lazy; + this.loading = false; + this.success = false; + } + + public int getId() { + return id; + } + + public int getNum() { + return num; + } + + public String[] getUrls() { + return urls; + } + + public String getUrl() { + return urls[0]; + } + + public void setUrl(String url) { + this.urls = new String[]{url}; + } + + public String getChapter() { + return chapter; + } + + public void setChapter(String chapter) { + this.chapter = chapter; + } + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = state; + } + + public void nextState() { + this.state++; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public long getSize() { + return height * width; + } + + public boolean isLazy() { + return lazy; + } + + public void setLazy(boolean lazy) { + this.lazy = lazy; + } + + public boolean isLoading() { + return loading; + } + + public void setLoading(boolean loading) { + this.loading = loading; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean isDownload() { + return download; + } + + public void setDownload(boolean download) { + this.download = download; + } + + @Override + public boolean equals(Object o) { + return o instanceof ImageUrl && ((ImageUrl) o).id == id; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/model/MiniComic.java b/app/src/main/java/com/hiroshi/cimoc/model/MiniComic.java index 05fb7030..2e532795 100644 --- a/app/src/main/java/com/hiroshi/cimoc/model/MiniComic.java +++ b/app/src/main/java/com/hiroshi/cimoc/model/MiniComic.java @@ -10,16 +10,9 @@ public class MiniComic { private String cid; private String title; private String cover; - private String update; - - public MiniComic(Long id, int source, String cid, String title, String cover, String update) { - this.id = id; - this.source = source; - this.cid = cid; - this.title = title; - this.cover = cover; - this.update = update; - } + private Boolean finish; + private boolean highlight; + private boolean local; public MiniComic(Comic comic) { this.id = comic.getId(); @@ -27,7 +20,9 @@ public MiniComic(Comic comic) { this.cid = comic.getCid(); this.title = comic.getTitle(); this.cover = comic.getCover(); - this.update = comic.getUpdate(); + this.finish = comic.getFinish(); + this.highlight = comic.getHighlight(); + this.local = comic.getLocal(); } @Override @@ -35,16 +30,33 @@ public boolean equals(Object o) { return o instanceof MiniComic && ((MiniComic) o).id.equals(id); } - public String getUpdate() { - return this.update; + @Override + public int hashCode() { + return id == null ? super.hashCode() : id.hashCode(); + } + + public Boolean isFinish() { + return finish; + } + + public void setFinish(boolean finish) { + this.finish = finish; + } + + public boolean isHighlight() { + return highlight; + } + + public void setHighlight(boolean highlight) { + this.highlight = highlight; } - public void setUpdate(String update) { - this.update = update; + public boolean isLocal() { + return local; } public String getCover() { - return this.cover; + return cover; } public void setCover(String cover) { @@ -52,7 +64,7 @@ public void setCover(String cover) { } public String getTitle() { - return this.title; + return title; } public void setTitle(String title) { @@ -60,7 +72,7 @@ public void setTitle(String title) { } public String getCid() { - return this.cid; + return cid; } public void setCid(String cid) { @@ -68,7 +80,7 @@ public void setCid(String cid) { } public int getSource() { - return this.source; + return source; } public void setSource(int source) { @@ -76,7 +88,7 @@ public void setSource(int source) { } public Long getId() { - return this.id; + return id; } public void setId(Long id) { diff --git a/app/src/main/java/com/hiroshi/cimoc/model/Source.java b/app/src/main/java/com/hiroshi/cimoc/model/Source.java new file mode 100644 index 00000000..21203e60 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/model/Source.java @@ -0,0 +1,78 @@ +package com.hiroshi.cimoc.model; + +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.NotNull; +import org.greenrobot.greendao.annotation.Unique; + +/** + * Created by Hiroshi on 2016/8/11. + */ +@Entity +public class Source { + + @Id + private Long id; + @NotNull + private String title; + @Unique + private int type; + @NotNull + private boolean enable; + + @Generated(hash = 615387317) + public Source() { + } + + @Generated(hash = 1339691905) + public Source(Long id, @NotNull String title, int type, boolean enable) { + this.id = id; + this.title = title; + this.type = type; + this.enable = enable; + } + + @Override + public boolean equals(Object o) { + return o instanceof Source && ((Source) o).id.equals(id); + } + + @Override + public int hashCode() { + return id == null ? super.hashCode() : id.hashCode(); + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getType() { + return this.type; + } + + public void setType(int type) { + this.type = type; + } + + public boolean getEnable() { + return this.enable; + } + + public void setEnable(boolean enable) { + this.enable = enable; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/model/Tag.java b/app/src/main/java/com/hiroshi/cimoc/model/Tag.java new file mode 100644 index 00000000..0cb596a2 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/model/Tag.java @@ -0,0 +1,55 @@ +package com.hiroshi.cimoc.model; + +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.NotNull; + +/** + * Created by Hiroshi on 2016/10/10. + */ +@Entity +public class Tag { + + @Id(autoincrement = true) + private Long id; + @NotNull + private String title; + + @Generated(hash = 836804519) + public Tag(Long id, @NotNull String title) { + this.id = id; + this.title = title; + } + + @Generated(hash = 1605720318) + public Tag() { + } + + @Override + public boolean equals(Object o) { + return o instanceof Tag && ((Tag) o).id.equals(id); + } + + @Override + public int hashCode() { + return id == null ? super.hashCode() : id.hashCode(); + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/model/TagRef.java b/app/src/main/java/com/hiroshi/cimoc/model/TagRef.java new file mode 100644 index 00000000..e5ee97f9 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/model/TagRef.java @@ -0,0 +1,56 @@ +package com.hiroshi.cimoc.model; + +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.NotNull; + +/** + * Created by Hiroshi on 2016/10/10. + */ +@Entity +public class TagRef { + + @Id(autoincrement = true) + private Long id; + @NotNull + private long tid; + @NotNull + private long cid; + + @Generated(hash = 1744842042) + public TagRef(Long id, long tid, long cid) { + this.id = id; + this.tid = tid; + this.cid = cid; + } + + @Generated(hash = 942776696) + public TagRef() { + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public long getTid() { + return this.tid; + } + + public void setTid(long tid) { + this.tid = tid; + } + + public long getCid() { + return this.cid; + } + + public void setCid(long cid) { + this.cid = cid; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/model/Task.java b/app/src/main/java/com/hiroshi/cimoc/model/Task.java new file mode 100644 index 00000000..a6f0f82a --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/model/Task.java @@ -0,0 +1,185 @@ +package com.hiroshi.cimoc.model; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.NotNull; +import org.greenrobot.greendao.annotation.Transient; + +/** + * Created by Hiroshi on 2016/9/1. + */ +@Entity +public class Task implements Parcelable { + + public static final int STATE_FINISH = 0; + public static final int STATE_PAUSE = 1; + public static final int STATE_PARSE = 2; + public static final int STATE_DOING = 3; + public static final int STATE_WAIT = 4; + public static final int STATE_ERROR = 5; + public final static Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Task createFromParcel(Parcel source) { + return new Task(source); + } + + @Override + public Task[] newArray(int size) { + return new Task[size]; + } + }; + @Id(autoincrement = true) + private Long id; + @NotNull + private long key; // 漫画主键 + @NotNull + private String path; + @NotNull + private String title; + @NotNull + private int progress; + @NotNull + private int max; + @Transient + private int source; + @Transient + private String cid; // 漫画 ID + @Transient + private int state; + + public Task(Parcel source) { + this.id = source.readLong(); + this.key = source.readLong(); + this.path = source.readString(); + this.title = source.readString(); + this.progress = source.readInt(); + this.max = source.readInt(); + this.source = source.readInt(); + this.cid = source.readString(); + this.state = source.readInt(); + } + + @Generated(hash = 1668809946) + public Task(Long id, long key, @NotNull String path, @NotNull String title, int progress, + int max) { + this.id = id; + this.key = key; + this.path = path; + this.title = title; + this.progress = progress; + this.max = max; + } + + @Generated(hash = 733837707) + public Task() { + } + + @Override + public boolean equals(Object o) { + return o instanceof Task && ((Task) o).id.equals(id); + } + + @Override + public int hashCode() { + return id == null ? super.hashCode() : id.hashCode(); + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public long getKey() { + return this.key; + } + + public void setKey(long key) { + this.key = key; + } + + public String getPath() { + return this.path; + } + + public void setPath(String path) { + this.path = path; + } + + public int getProgress() { + return this.progress; + } + + public void setProgress(int progress) { + this.progress = progress; + } + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getMax() { + return this.max; + } + + public void setMax(int max) { + this.max = max; + } + + public int getState() { + return this.state; + } + + public void setState(int state) { + this.state = state; + } + + public int getSource() { + return this.source; + } + + public void setSource(int source) { + this.source = source; + } + + public String getCid() { + return cid; + } + + public void setCid(String cid) { + this.cid = cid; + } + + public boolean isFinish() { + return max != 0 && progress == max; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(id); + dest.writeLong(key); + dest.writeString(path); + dest.writeString(title); + dest.writeInt(progress); + dest.writeInt(max); + dest.writeInt(source); + dest.writeString(cid); + dest.writeInt(state); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/network/DNSHelper.java b/app/src/main/java/com/hiroshi/cimoc/network/DNSHelper.java new file mode 100644 index 00000000..e314b1dd --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/network/DNSHelper.java @@ -0,0 +1,17 @@ +package com.hiroshi.cimoc.network; + +public class DNSHelper { + + static public String getIpByHost(String hostname) { + switch (hostname) { + // case "m.manhuagui.com": + // case "tw.manhuagui.com": + // return "47.89.23.88"; + // case "www.manhuagui.com": + // return "14.49.38.185"; + //return "106.185.40.107"; + default: + return null; + } + } +} diff --git a/app/src/main/java/com/hiroshi/cimoc/network/HttpDns.java b/app/src/main/java/com/hiroshi/cimoc/network/HttpDns.java new file mode 100644 index 00000000..7c19075b --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/network/HttpDns.java @@ -0,0 +1,26 @@ +package com.hiroshi.cimoc.network; + +import android.util.Log; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.List; + +import okhttp3.Dns; + +public class HttpDns implements Dns { + private static final Dns SYSTEM = Dns.SYSTEM; + + @Override + public List lookup(String hostname) throws UnknownHostException { + Log.e("HttpDns", "lookup:" + hostname); + String ip = DNSHelper.getIpByHost(hostname); + if (ip != null && !ip.equals("")) { + List inetAddresses = Arrays.asList(InetAddress.getAllByName(ip)); + Log.e("HttpDns", "inetAddresses:" + inetAddresses); + return inetAddresses; //返回自己解析的地址列表 + } + return SYSTEM.lookup(hostname); //走系统的dns列表 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hiroshi/cimoc/parser/Category.java b/app/src/main/java/com/hiroshi/cimoc/parser/Category.java new file mode 100644 index 00000000..dc3d68e5 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/parser/Category.java @@ -0,0 +1,56 @@ +package com.hiroshi.cimoc.parser; + +import androidx.annotation.IntDef; +import android.util.Pair; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + +/** + * Created by Hiroshi on 2016/12/10. + */ + +public interface Category { + + /** + * 主题 地区 读者 年份 进度 排序 + * TODO 根据不同图源定制类别 + */ + int CATEGORY_SUBJECT = 0; + int CATEGORY_AREA = 1; + int CATEGORY_READER = 2; + int CATEGORY_YEAR = 3; + int CATEGORY_PROGRESS = 4; + int CATEGORY_ORDER = 5; + + /** + * 选项是否可以组合,例如有些网站可以根据几个选项一起搜索 + */ + boolean isComposite(); + + /** + * 获取最终的格式化字符串,一般需要含有一个 %d 用于填充页码 + * TODO 这里不要返回 String 返回数组 + * + * @param args 各个选项的值,按照定义的顺序 + */ + String getFormat(String... args); + + /** + * 判断是否存在某个选项,用于确定界面中的 Spinner + */ + boolean hasAttribute(@Attribute int attr); + + /** + * 获取选项列表 + * 左边的 String 为显示的名称,右边的 String 为用来构造 url 的值 + */ + List> getAttrList(@Attribute int attr); + + @IntDef({CATEGORY_SUBJECT, CATEGORY_AREA, CATEGORY_READER, CATEGORY_YEAR, CATEGORY_PROGRESS, CATEGORY_ORDER}) + @Retention(RetentionPolicy.SOURCE) + @interface Attribute { + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/parser/JsonIterator.java b/app/src/main/java/com/hiroshi/cimoc/parser/JsonIterator.java new file mode 100644 index 00000000..bb03dcea --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/parser/JsonIterator.java @@ -0,0 +1,44 @@ +package com.hiroshi.cimoc.parser; + +import com.hiroshi.cimoc.model.Comic; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by Hiroshi on 2016/9/21. + */ + +public abstract class JsonIterator implements SearchIterator { + + private int index; + private JSONArray array; + + public JsonIterator(JSONArray array) { + this.index = 0; + this.array = array; + } + + @Override + public boolean hasNext() { + return index < array.length(); + } + + @Override + public Comic next() { + try { + return parse(array.getJSONObject(index++)); + } catch (JSONException e) { + return null; + } + } + + @Override + public boolean empty() { + return array == null || array.length() == 0; + } + + protected abstract Comic parse(JSONObject object) throws JSONException; + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/parser/MangaCategory.java b/app/src/main/java/com/hiroshi/cimoc/parser/MangaCategory.java new file mode 100644 index 00000000..38e0d9bd --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/parser/MangaCategory.java @@ -0,0 +1,98 @@ +package com.hiroshi.cimoc.parser; + +import android.util.Pair; + +import java.util.List; + +/** + * Created by Hiroshi on 2016/12/10. + */ + +public abstract class MangaCategory implements Category { + + @Override + public boolean isComposite() { + return false; + } + + protected abstract List> getSubject(); + + protected boolean hasArea() { + return false; + } + + protected List> getArea() { + return null; + } + + protected boolean hasReader() { + return false; + } + + protected List> getReader() { + return null; + } + + protected boolean hasProgress() { + return false; + } + + protected List> getProgress() { + return null; + } + + protected boolean hasYear() { + return false; + } + + protected List> getYear() { + return null; + } + + protected boolean hasOrder() { + return false; + } + + protected List> getOrder() { + return null; + } + + @Override + public boolean hasAttribute(@Attribute int attr) { + switch (attr) { + case CATEGORY_SUBJECT: + return true; + case CATEGORY_AREA: + return hasArea(); + case CATEGORY_READER: + return hasReader(); + case CATEGORY_PROGRESS: + return hasProgress(); + case CATEGORY_YEAR: + return hasYear(); + case CATEGORY_ORDER: + return hasOrder(); + } + return false; + } + + @Override + public List> getAttrList(@Attribute int attr) { + switch (attr) { + case CATEGORY_SUBJECT: + return getSubject(); + case CATEGORY_AREA: + return getArea(); + case CATEGORY_READER: + return getReader(); + case CATEGORY_PROGRESS: + return getProgress(); + case CATEGORY_YEAR: + return getYear(); + case CATEGORY_ORDER: + return getOrder(); + } + return null; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/parser/MangaParser.java b/app/src/main/java/com/hiroshi/cimoc/parser/MangaParser.java new file mode 100644 index 00000000..0c98f54f --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/parser/MangaParser.java @@ -0,0 +1,136 @@ +package com.hiroshi.cimoc.parser; + +import android.net.Uri; + +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by Hiroshi on 2016/8/22. + */ +public abstract class MangaParser implements Parser { + + protected String mTitle; + protected List filter = new ArrayList<>(); + private Category mCategory; + + protected void init(Source source, Category category) { + mTitle = source.getTitle(); + mCategory = category; + + initUrlFilterList(); + } + + protected void initUrlFilterList() { +// filter.add(new UrlFilter("manhua.dmzj.com", "/(\\w+)", 1)); + } + + @Override + public Request getChapterRequest(String html, String cid) { + return null; + } + + @Override + public Request getLazyRequest(String url) { + return null; + } + + @Override + public String parseLazy(String html, String url) { + return null; + } + + @Override + public Request getCheckRequest(String cid) { + return null; + } + + @Override + public String parseCheck(String html) { + return null; + } + + @Override + public Category getCategory() { + return mCategory; + } + + @Override + public Request getCategoryRequest(String format, int page) { + String url = StringUtils.format(format, page); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseCategory(String html, int page) { + return null; + } + + @Override + public String getTitle() { + return mTitle; + } + + protected String[] buildUrl(String path, String[] servers) { + if (servers != null) { + String[] url = new String[servers.length]; + for (int i = 0; i != servers.length; ++i) { + url[i] = servers[i].concat(path); + } + return url; + } + return null; + } + + protected boolean isFinish(String text) { + return text != null && (text.contains("完结") || text.contains("Completed")); + } + + @Override + public String getUrl(String cid) { + return cid; + } + + @Override + public Headers getHeader() { + return null; + } + + @Override + public Headers getHeader(String url) { + return getHeader(); + } + + @Override + public Headers getHeader(List list) { + return getHeader(); + } + + @Override + public boolean isHere(Uri uri) { + boolean val = false; + for (UrlFilter uf : filter) { + val |= (uri.getHost().indexOf(uf.Filter) != -1); + } + return val; + } + + @Override + public String getComicId(Uri uri) { + for (UrlFilter uf : filter) { + if (uri.getHost().indexOf(uf.Filter) != -1) { + return StringUtils.match(uf.Regex, uri.getPath(), uf.Group); + } + } + return null; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/parser/NodeIterator.java b/app/src/main/java/com/hiroshi/cimoc/parser/NodeIterator.java new file mode 100644 index 00000000..27cc47ef --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/parser/NodeIterator.java @@ -0,0 +1,38 @@ +package com.hiroshi.cimoc.parser; + +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.soup.Node; + +import java.util.List; +import java.util.ListIterator; + +/** + * Created by Hiroshi on 2016/9/21. + */ + +public abstract class NodeIterator implements SearchIterator { + + private ListIterator iterator; + + protected NodeIterator(List list) { + this.iterator = list.isEmpty() ? null : list.listIterator(); + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Comic next() { + return parse(iterator.next()); + } + + @Override + public boolean empty() { + return iterator == null; + } + + protected abstract Comic parse(Node node); + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/parser/Parser.java b/app/src/main/java/com/hiroshi/cimoc/parser/Parser.java new file mode 100644 index 00000000..718ba2f9 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/parser/Parser.java @@ -0,0 +1,170 @@ +package com.hiroshi.cimoc.parser; + +import android.net.Uri; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.source.DM5; +//import com.hiroshi.cimoc.source.HHSSEE; +import com.hiroshi.cimoc.source.IKanman; + +import org.json.JSONException; + +import java.io.UnsupportedEncodingException; +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by Hiroshi on 2016/8/22. + */ +public interface Parser { + + /** + * 搜索页的 HTTP 请求 + * + * @param keyword 关键字 + * @param page 页码 + */ + Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException; + + /** + * 获取搜索结果迭代器,这里不直接解析成列表是为了多图源搜索时,不同图源漫画穿插的效果 + * + * @param html 页面源代码 + * @param page 页码,可能对于一些判断有用 + */ + SearchIterator getSearchIterator(String html, int page) throws JSONException; + + /** + * 详情页的 HTTP 请求 + * + * @param cid 漫画 ID + */ + Request getInfoRequest(String cid); + + /** + * 解析详情 + * + * @param html 页面源代码 + * @param comic 漫画实体类,需要设置其中的字段 + */ + void parseInfo(String html, Comic comic) throws UnsupportedEncodingException; + + /** + * 章节列表的 HTTP 请求,若在 {@link #parseInfo} 中可以解析出章节列表,返回 null,代表不用再次解析 + * + * @param html 详情页面源代码,与 {@link #parseInfo} 中的第一个参数相同 + * @param cid 漫画 ID + * @see MangaParser#getChapterRequest + */ + Request getChapterRequest(String html, String cid); + + /** + * 解析章节列表 + * + * @param html 页面源代码 + */ + List parseChapter(String html); + + /** + * 图片列表的 HTTP 请求 + * + * @param cid 漫画 ID + * @param path 章节路径 + */ + Request getImagesRequest(String cid, String path); + + /** + * 解析图片列表,若为惰性加载,则 {@link ImageUrl#lazy} 为 true + * 惰性加载的情况,一次性不能拿到所有图片链接,例如网站使用了多次异步请求 {@link DM5#parseImages},或需要跳转到不同页面 + * 才能获取 {@link HHSSEE#parseImages},这些情况一般可以根据页码构造出相应的请求链接,到阅读时再解析 + * 支持多个链接 {@link ImageUrl#urls},例如 {@link IKanman#parseImages} + * + * @param html 页面源代码 + */ + List parseImages(String html); + + /** + * 图片惰性加载的 HTTP 请求 + * + * @param url 请求链接 + */ + Request getLazyRequest(String url); + + /** + * 解析图片链接 + * + * @param html 页面源代码 + * @param url 请求链接,可能需要其中的参数 + */ + String parseLazy(String html, String url); + + /** + * 检查更新的 HTTP 请求,一般与 {@link #getInfoRequest} 相同 + * + * @param cid 漫画 ID + */ + Request getCheckRequest(String cid); + + /** + * 解析最后更新时间,用于与原来的比较,一般与 {@link #parseInfo} 获取 {@link Comic#update} 字段的方式相同 + * + * @param html 页面源代码 + */ + String parseCheck(String html); + + /** + * 获取漫画分类 + * + * @see Category + */ + Category getCategory(); + + /** + * 获取分类的 HTTP 请求 + * + * @param format 格式化字符串,包含一个 %d 用于页码 + * @param page 页码 + */ + Request getCategoryRequest(String format, int page); + + /** + * 解析分类 + * + * @param html 页面源代码 + * @param page 页面,可能对于一些判断有用 + */ + List parseCategory(String html, int page); + + /** + * 获取图源标题,为了方便强行塞进来的 + */ + String getTitle(); + + /** + * 获取下载图片时的 HTTP 请求头,一般用来设置 Referer 和 Cookie + */ + Headers getHeader(); + + Headers getHeader(String url); + + Headers getHeader(List list); + + /* + * 设置当前漫画url,以便分享使用。 + */ + String getUrl(String cid); + + /** + * 检测host是否属于改漫画 + */ + boolean isHere(Uri uri); + + /** + * 根据uri返回comic id + */ + String getComicId(Uri uri); +} diff --git a/app/src/main/java/com/hiroshi/cimoc/parser/RegexIterator.java b/app/src/main/java/com/hiroshi/cimoc/parser/RegexIterator.java new file mode 100644 index 00000000..ecbb370b --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/parser/RegexIterator.java @@ -0,0 +1,39 @@ +package com.hiroshi.cimoc.parser; + +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.soup.Node; + +import java.util.List; +import java.util.ListIterator; +import java.util.regex.Matcher; + +/** + * Created by Hiroshi on 2016/9/21. + */ + +public abstract class RegexIterator implements SearchIterator { + + private Matcher match; + + protected RegexIterator(Matcher match) { + this.match = match; + } + + @Override + public boolean hasNext() { + return match.find(); + } + + @Override + public Comic next() { + return parse(match); + } + + @Override + public boolean empty() { + return false; + } + + protected abstract Comic parse(Matcher match); + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/parser/SearchIterator.java b/app/src/main/java/com/hiroshi/cimoc/parser/SearchIterator.java new file mode 100644 index 00000000..e17d0189 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/parser/SearchIterator.java @@ -0,0 +1,17 @@ +package com.hiroshi.cimoc.parser; + +import com.hiroshi.cimoc.model.Comic; + +/** + * Created by Hiroshi on 2016/9/21. + */ + +public interface SearchIterator { + + boolean empty(); + + boolean hasNext(); + + Comic next(); + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/parser/UrlFilter.java b/app/src/main/java/com/hiroshi/cimoc/parser/UrlFilter.java new file mode 100644 index 00000000..bb8bdfcb --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/parser/UrlFilter.java @@ -0,0 +1,25 @@ +package com.hiroshi.cimoc.parser; + +public class UrlFilter { + public String Filter; + public String Regex; + public int Group; + + public UrlFilter(String filter) { + Filter = filter; + Regex = "(\\d+)"; + Group = 1; + } + + public UrlFilter(String filter, String regex) { + Filter = filter; + Regex = regex; + Group = 1; + } + + public UrlFilter(String filter, String regex, int group) { + Filter = filter; + Regex = regex; + Group = group; + } +} diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/AboutPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/AboutPresenter.java new file mode 100644 index 00000000..88cb42bc --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/AboutPresenter.java @@ -0,0 +1,34 @@ +package com.hiroshi.cimoc.presenter; + +import com.hiroshi.cimoc.core.Update; +import com.hiroshi.cimoc.ui.view.AboutView; + +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; + +/** + * Created by Hiroshi on 2016/8/24. + */ +public class AboutPresenter extends BasePresenter { + + public void checkUpdate(final String version) { + mCompositeSubscription.add(Update.check() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(String s) { + if (version.equals(s)) { + mBaseView.onUpdateNone(); + } else { + mBaseView.onUpdateReady(); + } + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onCheckError(); + } + })); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/BackupPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/BackupPresenter.java new file mode 100644 index 00000000..5dc95040 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/BackupPresenter.java @@ -0,0 +1,368 @@ +package com.hiroshi.cimoc.presenter; + +import android.content.ContentResolver; +import android.util.Pair; + +import com.hiroshi.cimoc.App; +import com.hiroshi.cimoc.core.Backup; +import com.hiroshi.cimoc.manager.ComicManager; +import com.hiroshi.cimoc.manager.TagManager; +import com.hiroshi.cimoc.manager.TagRefManager; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.MiniComic; +import com.hiroshi.cimoc.model.Tag; +import com.hiroshi.cimoc.model.TagRef; +import com.hiroshi.cimoc.rx.RxBus; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.ui.view.BackupView; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import rx.Observable; +import rx.Subscriber; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +/** + * Created by Hiroshi on 2016/10/19. + */ + +public class BackupPresenter extends BasePresenter { + + private ComicManager mComicManager; + private TagManager mTagManager; + private TagRefManager mTagRefManager; + private ContentResolver mContentResolver; + + @Override + protected void onViewAttach() { + mComicManager = ComicManager.getInstance(mBaseView); + mTagManager = TagManager.getInstance(mBaseView); + mTagRefManager = TagRefManager.getInstance(mBaseView); + mContentResolver = mBaseView.getAppInstance().getContentResolver(); + } + + public void loadComicFile() { + mCompositeSubscription.add(Backup.loadFavorite(mBaseView.getAppInstance().getDocumentFile()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(String[] file) { + mBaseView.onComicFileLoadSuccess(file); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onFileLoadFail(); + } + })); + } + + public void loadTagFile() { + mCompositeSubscription.add(Backup.loadTag(mBaseView.getAppInstance().getDocumentFile()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(String[] file) { + mBaseView.onTagFileLoadSuccess(file); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onFileLoadFail(); + } + })); + } + + public void loadSettingsFile() { + mCompositeSubscription.add(Backup.loadSettings(mBaseView.getAppInstance().getDocumentFile()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(String[] file) { + mBaseView.onSettingsFileLoadSuccess(file); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onFileLoadFail(); + } + })); + } + + public void saveComic() { + mCompositeSubscription.add(mComicManager.listFavoriteOrHistoryInRx() + .map(new Func1, Integer>() { + @Override + public Integer call(List list) { + return Backup.saveComic(mContentResolver, mBaseView.getAppInstance().getDocumentFile(), list); + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(Integer size) { + if (size == -1) { + mBaseView.onBackupSaveFail(); + } else { + mBaseView.onBackupSaveSuccess(size); + } + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onBackupSaveFail(); + } + })); + } + + public void saveTag() { + mCompositeSubscription.add(Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + int size = groupAndSaveComicByTag(); + subscriber.onNext(size); + subscriber.onCompleted(); + } + }).subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(Integer size) { + if (size == -1) { + mBaseView.onBackupSaveFail(); + } else { + mBaseView.onBackupSaveSuccess(size); + } + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onBackupSaveFail(); + } + })); + } + + public void saveSettings() { + mCompositeSubscription.add(Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { +// boolean isBackuped = SharePrefUtils.copyFiles(context.getFilesDir().getPath() + context.getPackageName() + "/shared_prefs", ); + int size = Backup.saveSetting(mContentResolver, mBaseView.getAppInstance().getDocumentFile(), App.getPreferenceManager().getAll()); + subscriber.onNext(size); + subscriber.onCompleted(); + } + }).subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(Integer size) { + if (size == -1) { + mBaseView.onBackupSaveFail(); + } else { + mBaseView.onBackupSaveSuccess(size); + } + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onBackupSaveFail(); + } + })); + } + + public void restoreComic(String filename) { + mCompositeSubscription.add(Backup.restoreComic(mContentResolver, mBaseView.getAppInstance().getDocumentFile(), filename) + .doOnNext(new Action1>() { + @Override + public void call(List list) { + filterAndPostComic(list); + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onBackupRestoreSuccess(); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + throwable.printStackTrace(); + mBaseView.onBackupRestoreFail(); + } + })); + } + + public void restoreTag(String filename) { + mCompositeSubscription.add(Backup.restoreTag(mContentResolver, mBaseView.getAppInstance().getDocumentFile(), filename) + .doOnNext(new Action1>>>() { + @Override + public void call(List>> list) { + updateAndPostTag(list); + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>>>() { + @Override + public void call(List>> pair) { + mBaseView.onBackupRestoreSuccess(); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onBackupRestoreFail(); + } + })); + } + + public void restoreSetting(String filename) { + mCompositeSubscription.add(Backup.restoreSetting(mContentResolver, mBaseView.getAppInstance().getDocumentFile(), filename) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(Map pair) { + mBaseView.onBackupRestoreSuccess(); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onBackupRestoreFail(); + } + })); + } + + private List setTagsId(final List>> list) { + final List tags = new LinkedList<>(); + mTagRefManager.runInTx(new Runnable() { + @Override + public void run() { + for (Pair> pair : list) { + Tag tag = mTagManager.load(pair.first.getTitle()); + if (tag == null) { + mTagManager.insert(pair.first); + tags.add(pair.first); + } else { + pair.first.setId(tag.getId()); + } + } + } + }); + return tags; + } + + private void updateAndPostTag(final List>> list) { + List tags = setTagsId(list); + for (Pair> pair : list) { + filterAndPostComic(pair.second); + } + mTagRefManager.runInTx(new Runnable() { + @Override + public void run() { + for (Pair> pair : list) { + long tid = pair.first.getId(); + for (Comic comic : pair.second) { + TagRef ref = mTagRefManager.load(tid, comic.getId()); + if (ref == null) { + mTagRefManager.insert(new TagRef(null, tid, comic.getId())); + } + } + } + } + }); + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_TAG_RESTORE, tags)); + } + + private int groupAndSaveComicByTag() { + final List>> list = new LinkedList<>(); + mComicManager.runInTx(new Runnable() { + @Override + public void run() { + for (Tag tag : mTagManager.list()) { + List comics = new LinkedList<>(); + Pair> pair = Pair.create(tag, comics); + for (TagRef ref : mTagRefManager.listByTag(tag.getId())) { + comics.add(mComicManager.load(ref.getCid())); + } + list.add(pair); + } + } + }); + return Backup.saveTag(mContentResolver, mBaseView.getAppInstance().getDocumentFile(), list); + } + + private void filterAndPostComic(final List list) { + final List favorite = new LinkedList<>(); + final List history = new LinkedList<>(); + mComicManager.runInTx(new Runnable() { + @Override + public void run() { + for (Comic comic : list) { + Comic temp = mComicManager.load(comic.getSource(), comic.getCid()); + if (temp == null) { + mComicManager.insert(comic); + if (comic.getHistory() != null) { + history.add(comic); + } + if (comic.getFavorite() != null) { + favorite.add(comic); + } + } else { + if (temp.getFavorite() == null || temp.getHistory() == null) { + if (temp.getFavorite() == null && comic.getFavorite() != null) { + temp.setFavorite(comic.getFavorite()); + favorite.add(comic); + } + if (temp.getHistory() == null && comic.getHistory() != null) { + temp.setHistory(comic.getHistory()); + if (temp.getLast() == null) { + temp.setLast(comic.getLast()); + temp.setPage(comic.getPage()); + temp.setChapter(comic.getChapter()); + } + history.add(comic); + } + mComicManager.update(temp); + } + // TODO 可能要设置其他域 + comic.setId(temp.getId()); + } + } + } + }); + postComic(favorite, history); + } + + private void postComic(List favorite, List history) { +/* Collections.sort(favorite, new Comparator() { + @Override + public int compare(Comic lhs, Comic rhs) { + return (int) (lhs.getFavorite() - rhs.getFavorite()); + } + }); + Collections.sort(history, new Comparator() { + @Override + public int compare(Comic lhs, Comic rhs) { + return (int) (lhs.getHistory() - rhs.getHistory()); + } + }); */ + RxBus.getInstance().post( + new RxEvent(RxEvent.EVENT_COMIC_FAVORITE_RESTORE, convertToMiniComic(favorite))); + RxBus.getInstance().post( + new RxEvent(RxEvent.EVENT_COMIC_HISTORY_RESTORE, convertToMiniComic(history))); + } + + private List convertToMiniComic(List list) { + List result = new ArrayList<>(list.size()); + for (Comic comic : list) { + result.add(new MiniComic(comic)); + } + return result; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/BasePresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/BasePresenter.java index bb7acd88..6c8f36d7 100644 --- a/app/src/main/java/com/hiroshi/cimoc/presenter/BasePresenter.java +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/BasePresenter.java @@ -1,18 +1,53 @@ package com.hiroshi.cimoc.presenter; -import org.greenrobot.eventbus.EventBus; +import com.hiroshi.cimoc.rx.RxBus; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.ui.view.BaseView; + +import rx.functions.Action1; +import rx.subscriptions.CompositeSubscription; /** * Created by Hiroshi on 2016/7/4. */ -public abstract class BasePresenter { +public abstract class BasePresenter { + + protected T mBaseView; + protected CompositeSubscription mCompositeSubscription; + + public void attachView(T view) { + this.mBaseView = view; + onViewAttach(); + mCompositeSubscription = new CompositeSubscription(); + addSubscription(RxEvent.EVENT_SWITCH_NIGHT, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.onNightSwitch(); + } + }); + initSubscription(); + } + + protected void onViewAttach() { + } + + protected void initSubscription() { + } - public void onCreate() { - EventBus.getDefault().register(this); + protected void addSubscription(@RxEvent.EventType int type, Action1 action) { + mCompositeSubscription.add(RxBus.getInstance().toObservable(type).subscribe(action, new Action1() { + @Override + public void call(Throwable throwable) { + throwable.printStackTrace(); + } + })); } - public void onDestroy() { - EventBus.getDefault().unregister(this); + public void detachView() { + if (mCompositeSubscription != null) { + mCompositeSubscription.unsubscribe(); + } + mBaseView = null; } } diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/ComicPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/ComicPresenter.java new file mode 100644 index 00000000..a9b7bbe0 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/ComicPresenter.java @@ -0,0 +1,41 @@ +package com.hiroshi.cimoc.presenter; + +import com.hiroshi.cimoc.manager.TagManager; +import com.hiroshi.cimoc.model.Tag; +import com.hiroshi.cimoc.ui.view.ComicView; + +import java.util.List; + +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; + +/** + * Created by Hiroshi on 2016/10/11. + */ + +public class ComicPresenter extends BasePresenter { + + private TagManager mTagManager; + + @Override + protected void onViewAttach() { + mTagManager = TagManager.getInstance(mBaseView); + } + + public void loadTag() { + mCompositeSubscription.add(mTagManager.listInRx() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onTagLoadSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onTagLoadFail(); + } + })); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/DetailPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/DetailPresenter.java index 09754fdc..9cea63b5 100644 --- a/app/src/main/java/com/hiroshi/cimoc/presenter/DetailPresenter.java +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/DetailPresenter.java @@ -1,102 +1,233 @@ package com.hiroshi.cimoc.presenter; -import android.content.Intent; - -import com.hiroshi.cimoc.R; -import com.hiroshi.cimoc.core.ComicManager; -import com.hiroshi.cimoc.core.Kami; -import com.hiroshi.cimoc.core.base.Manga; +import com.hiroshi.cimoc.core.Backup; +import com.hiroshi.cimoc.core.Download; +import com.hiroshi.cimoc.core.Manga; +import com.hiroshi.cimoc.manager.ComicManager; +import com.hiroshi.cimoc.manager.SourceManager; +import com.hiroshi.cimoc.manager.TagRefManager; +import com.hiroshi.cimoc.manager.TaskManager; import com.hiroshi.cimoc.model.Chapter; -import com.hiroshi.cimoc.ui.activity.DetailActivity; -import com.hiroshi.cimoc.ui.activity.ReaderActivity; -import com.hiroshi.cimoc.model.EventMessage; - -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.MiniComic; +import com.hiroshi.cimoc.model.Task; +import com.hiroshi.cimoc.rx.RxBus; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.ui.view.DetailView; -import java.util.LinkedList; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import rx.Observable; +import rx.Subscriber; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.schedulers.Schedulers; /** * Created by Hiroshi on 2016/7/4. */ -public class DetailPresenter extends BasePresenter { +public class DetailPresenter extends BasePresenter { - private DetailActivity mDetailActivity; private ComicManager mComicManager; - private Manga mManga; - - public DetailPresenter(DetailActivity activity) { - mDetailActivity = activity; - mComicManager = ComicManager.getInstance(); - mManga = Kami.getMangaById(mComicManager.getSource()); - } + private TaskManager mTaskManager; + private TagRefManager mTagRefManager; + private SourceManager mSourceManager; + private Comic mComic; @Override - public void onCreate() { - super.onCreate(); - mManga.into(mComicManager.getComic()); + protected void onViewAttach() { + mComicManager = ComicManager.getInstance(mBaseView); + mTaskManager = TaskManager.getInstance(mBaseView); + mTagRefManager = TagRefManager.getInstance(mBaseView); + mSourceManager = SourceManager.getInstance(mBaseView); } + @SuppressWarnings("unchecked") @Override - public void onDestroy() { - super.onDestroy(); - mManga.cancel(); + protected void initSubscription() { + addSubscription(RxEvent.EVENT_COMIC_UPDATE, new Action1() { + @Override + public void call(RxEvent rxEvent) { + if (mComic.getId() != null && mComic.getId() == (long) rxEvent.getData()) { + Comic comic = mComicManager.load(mComic.getId()); + mComic.setPage(comic.getPage()); + mComic.setLast(comic.getLast()); + mComic.setChapter(comic.getChapter()); + mBaseView.onLastChange(mComic.getLast()); + } + } + }); } - public void saveComic() { - mComicManager.saveComic(); + public void load(long id, int source, String cid) { + if (id == -1) { + mComic = mComicManager.loadOrCreate(source, cid); + } else { + mComic = mComicManager.load(id); + } + cancelHighlight(); + load(); } - public void onItemClick(int position) { - if (position != 0) { - Intent intent = ReaderActivity.createIntent(mDetailActivity, position - 1); - mDetailActivity.startActivity(intent); + private void updateChapterList(List list) { + Map map = new HashMap<>(); + for (Task task : mTaskManager.list(mComic.getId())) { + map.put(task.getPath(), task); + } + if (!map.isEmpty()) { + for (Chapter chapter : list) { + Task task = map.get(chapter.getPath()); + if (task != null) { + chapter.setDownload(true); + chapter.setCount(task.getProgress()); + chapter.setComplete(task.isFinish()); + } + } } } - public void onStarClick() { - if (mComicManager.isComicStar()) { - mComicManager.unfavoriteComic(); - mDetailActivity.setStarButtonRes(R.drawable.ic_favorite_border_white_24dp); - mDetailActivity.showSnackbar("取消收藏成功"); - } else { - mComicManager.favoriteComic(); - mDetailActivity.setStarButtonRes(R.drawable.ic_favorite_white_24dp); - mDetailActivity.showSnackbar("收藏成功"); + private void load() { + mCompositeSubscription.add(Manga.getComicInfo(mSourceManager.getParser(mComic.getSource()), mComic) + .doOnNext(new Action1>() { + @Override + public void call(List list) { + if (mComic.getId() != null) { + updateChapterList(list); + } + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onComicLoadSuccess(mComic); + mBaseView.onChapterLoadSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onComicLoadSuccess(mComic); + mBaseView.onParseError(); + } + })); + } + + private void cancelHighlight() { + if (mComic.getHighlight()) { + mComic.setHighlight(false); + mComic.setFavorite(System.currentTimeMillis()); + mComicManager.update(mComic); + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_COMIC_CANCEL_HIGHLIGHT, new MiniComic(mComic))); } } - @Subscribe(threadMode = ThreadMode.MAIN) - public void onEvent(EventMessage msg) { - switch (msg.getType()) { - case EventMessage.LOAD_COMIC_SUCCESS: - initView((List) msg.getData()); - break; - case EventMessage.LOAD_COMIC_FAIL: - initView(new LinkedList()); - break; - case EventMessage.AFTER_READ: - mDetailActivity.setLastChapter((String) msg.getData()); - break; - case EventMessage.NETWORK_ERROR: - mDetailActivity.hideProgressBar(); - mDetailActivity.showSnackbar("网络错误"); - break; + /** + * 更新最后阅读 + * + * @param path 最后阅读 + * @return 漫画ID + */ + public long updateLast(String path) { + if (mComic.getFavorite() != null) { + mComic.setFavorite(System.currentTimeMillis()); + } + mComic.setHistory(System.currentTimeMillis()); + if (!path.equals(mComic.getLast())) { + mComic.setLast(path); + mComic.setPage(1); } + mComicManager.updateOrInsert(mComic); + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_COMIC_READ, new MiniComic(mComic))); + return mComic.getId(); } - private void initView(List list) { - mComicManager.setChapters(list); - String last = mComicManager.getLast(); - mDetailActivity.initRecyclerView(mComicManager.getComic(), list, last); - int resId = mComicManager.isComicStar() ? R.drawable.ic_favorite_white_24dp : R.drawable.ic_favorite_border_white_24dp; - mDetailActivity.setStarButtonRes(resId); - mDetailActivity.setStarButtonVisible(); - mDetailActivity.hideProgressBar(); - if (list.isEmpty()) { - mDetailActivity.showSnackbar("解析错误或此漫画已被屏蔽"); + public Comic getComic() { + return mComic; + } + + public void backup() { + mComicManager.listFavoriteOrHistoryInRx() + .doOnNext(new Action1>() { + @Override + public void call(List list) { + Backup.saveComicAuto(mBaseView.getAppInstance().getContentResolver(), + mBaseView.getAppInstance().getDocumentFile(), list); + } + }) + .subscribe(); + } + + public void favoriteComic() { + mComic.setFavorite(System.currentTimeMillis()); + mComicManager.updateOrInsert(mComic); + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_COMIC_FAVORITE, new MiniComic(mComic))); + } + + public void unfavoriteComic() { + long id = mComic.getId(); + mComic.setFavorite(null); + mTagRefManager.deleteByComic(id); + mComicManager.updateOrDelete(mComic); + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_COMIC_UNFAVORITE, id)); + } + + private ArrayList getTaskList(List list) { + ArrayList result = new ArrayList<>(list.size()); + for (Chapter chapter : list) { + Task task = new Task(null, -1, chapter.getPath(), chapter.getTitle(), 0, 0); + task.setSource(mComic.getSource()); + task.setCid(mComic.getCid()); + task.setState(Task.STATE_WAIT); + result.add(task); } + return result; + } + + /** + * 添加任务到数据库 + * + * @param cList 所有章节列表,用于写索引文件 + * @param dList 下载章节列表 + */ + public void addTask(final List cList, final List dList) { + mCompositeSubscription.add(Observable.create(new Observable.OnSubscribe>() { + @Override + public void call(Subscriber> subscriber) { + final ArrayList result = getTaskList(dList); + mComic.setDownload(System.currentTimeMillis()); + mComicManager.runInTx(new Runnable() { + @Override + public void run() { + mComicManager.updateOrInsert(mComic); + for (Task task : result) { + task.setKey(mComic.getId()); + mTaskManager.insert(task); + } + } + }); + Download.updateComicIndex(mBaseView.getAppInstance().getContentResolver(), + mBaseView.getAppInstance().getDocumentFile(), cList, mComic); + subscriber.onNext(result); + subscriber.onCompleted(); + } + }).subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(ArrayList list) { + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_TASK_INSERT, new MiniComic(mComic), list)); + mBaseView.onTaskAddSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + + mBaseView.onTaskAddFail(); + } + })); } } diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/DownloadPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/DownloadPresenter.java new file mode 100644 index 00000000..cac0fa3d --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/DownloadPresenter.java @@ -0,0 +1,181 @@ +package com.hiroshi.cimoc.presenter; + +import androidx.collection.LongSparseArray; + +import com.hiroshi.cimoc.core.Download; +import com.hiroshi.cimoc.manager.ComicManager; +import com.hiroshi.cimoc.manager.SourceManager; +import com.hiroshi.cimoc.manager.TaskManager; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.MiniComic; +import com.hiroshi.cimoc.model.Task; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.rx.ToAnotherList; +import com.hiroshi.cimoc.ui.view.DownloadView; +import com.hiroshi.cimoc.utils.ComicUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +/** + * Created by Hiroshi on 2016/9/1. + */ +public class DownloadPresenter extends BasePresenter { + + private ComicManager mComicManager; + private TaskManager mTaskManager; + private SourceManager mSourceManager; + + @Override + protected void onViewAttach() { + mComicManager = ComicManager.getInstance(mBaseView); + mTaskManager = TaskManager.getInstance(mBaseView); + mSourceManager = SourceManager.getInstance(mBaseView); + } + + @SuppressWarnings("unchecked") + @Override + protected void initSubscription() { + addSubscription(RxEvent.EVENT_TASK_INSERT, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.onDownloadAdd((MiniComic) rxEvent.getData()); + } + }); + addSubscription(RxEvent.EVENT_DOWNLOAD_REMOVE, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.onDownloadDelete((long) rxEvent.getData()); + } + }); + addSubscription(RxEvent.EVENT_DOWNLOAD_CLEAR, new Action1() { + @Override + public void call(RxEvent rxEvent) { + for (long id : (List) rxEvent.getData()) { + mBaseView.onDownloadDelete(id); + } + } + }); + addSubscription(RxEvent.EVENT_DOWNLOAD_START, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.onDownloadStart(); + } + }); + addSubscription(RxEvent.EVENT_DOWNLOAD_STOP, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.onDownloadStop(); + } + }); + } + + public void deleteComic(long id) { + mCompositeSubscription.add(Observable.just(id) + .doOnNext(new Action1() { + @Override + public void call(final Long id) { + Comic comic = mComicManager.callInTx(new Callable() { + @Override + public Comic call() throws Exception { + Comic comic = mComicManager.load(id); + mTaskManager.deleteByComicId(id); + comic.setDownload(null); + mComicManager.updateOrDelete(comic); + return comic; + } + }); + Download.delete(mBaseView.getAppInstance().getDocumentFile(), comic, mSourceManager.getParser(comic.getSource()).getTitle()); + } + }).subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(Long id) { + mBaseView.onDownloadDeleteSuccess(id); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onExecuteFail(); + } + })); + } + + public Comic load(long id) { + return mComicManager.load(id); + } + + public void load() { + mCompositeSubscription.add(mComicManager.listDownloadInRx() + .compose(new ToAnotherList<>(new Func1() { + @Override + public MiniComic call(Comic comic) { + return new MiniComic(comic); + } + })) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onComicLoadSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onComicLoadFail(); + } + })); + } + + public void loadTask() { + mCompositeSubscription.add(mTaskManager.listInRx() + .flatMap(new Func1, Observable>() { + @Override + public Observable call(List list) { + return Observable.from(list); + } + }) + .filter(new Func1() { + @Override + public Boolean call(Task task) { + return !task.isFinish(); + } + }) + .toList() + .doOnNext(new Action1>() { + @Override + public void call(List list) { + LongSparseArray array = ComicUtils.buildComicMap(mComicManager.listDownload()); + for (Task task : list) { + Comic comic = array.get(task.getKey()); + if (comic != null) { + task.setSource(comic.getSource()); + task.setCid(comic.getCid()); + } + task.setState(Task.STATE_WAIT); + } + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onTaskLoadSuccess(new ArrayList<>(list)); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onExecuteFail(); + } + })); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/FavoritePresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/FavoritePresenter.java index 03f3d1cd..ee2751d4 100644 --- a/app/src/main/java/com/hiroshi/cimoc/presenter/FavoritePresenter.java +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/FavoritePresenter.java @@ -1,54 +1,146 @@ package com.hiroshi.cimoc.presenter; -import android.content.Intent; - -import com.hiroshi.cimoc.core.ComicManager; -import com.hiroshi.cimoc.model.EventMessage; +import com.hiroshi.cimoc.core.Manga; +import com.hiroshi.cimoc.manager.ComicManager; +import com.hiroshi.cimoc.manager.SourceManager; +import com.hiroshi.cimoc.manager.TagRefManager; +import com.hiroshi.cimoc.model.Comic; import com.hiroshi.cimoc.model.MiniComic; -import com.hiroshi.cimoc.ui.activity.DetailActivity; -import com.hiroshi.cimoc.ui.fragment.FavoriteFragment; - -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.rx.ToAnotherList; +import com.hiroshi.cimoc.ui.view.FavoriteView; import java.util.List; +import rx.Observer; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.functions.Func1; + /** * Created by Hiroshi on 2016/7/6. */ -public class FavoritePresenter extends BasePresenter { +public class FavoritePresenter extends BasePresenter { - private FavoriteFragment mFavoriteFragment; private ComicManager mComicManager; + private SourceManager mSourceManager; + private TagRefManager mTagRefManager; + + @Override + protected void onViewAttach() { + mComicManager = ComicManager.getInstance(mBaseView); + mSourceManager = SourceManager.getInstance(mBaseView); + mTagRefManager = TagRefManager.getInstance(mBaseView); + } + + @SuppressWarnings("unchecked") + @Override + protected void initSubscription() { + super.initSubscription(); + addSubscription(RxEvent.EVENT_COMIC_FAVORITE, new Action1() { + @Override + public void call(RxEvent rxEvent) { + MiniComic comic = (MiniComic) rxEvent.getData(); + mBaseView.OnComicFavorite(comic); + } + }); + addSubscription(RxEvent.EVENT_COMIC_UNFAVORITE, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.OnComicUnFavorite((long) rxEvent.getData()); + } + }); + addSubscription(RxEvent.EVENT_COMIC_FAVORITE_RESTORE, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.OnComicRestore((List) rxEvent.getData()); + } + }); + addSubscription(RxEvent.EVENT_COMIC_READ, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.onComicRead((MiniComic) rxEvent.getData()); + } + }); + addSubscription(RxEvent.EVENT_COMIC_CANCEL_HIGHLIGHT, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.onHighlightCancel((MiniComic) rxEvent.getData()); + } + }); + } + + public Comic load(long id) { + return mComicManager.load(id); + } - public FavoritePresenter(FavoriteFragment fragment) { - mFavoriteFragment = fragment; - mComicManager = ComicManager.getInstance(); + public void load() { + mCompositeSubscription.add(mComicManager.listFavoriteInRx() + .compose(new ToAnotherList<>(new Func1() { + @Override + public MiniComic call(Comic comic) { + return new MiniComic(comic); + } + })) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onComicLoadSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onComicLoadFail(); + } + })); } - public List getComic() { - return mComicManager.listFavorite(); + public void cancelAllHighlight() { + mComicManager.cancelHighlight(); } - public void onItemClick(int position) { - MiniComic comic = mFavoriteFragment.getItem(position); - Intent intent = DetailActivity.createIntent(mFavoriteFragment.getActivity(), comic.getId(), comic.getSource(), comic.getCid()); - mFavoriteFragment.startActivity(intent); + public void unfavoriteComic(long id) { + Comic comic = mComicManager.load(id); + comic.setFavorite(null); + mTagRefManager.deleteByComic(id); + mComicManager.updateOrDelete(comic); + mBaseView.OnComicUnFavorite(id); } - @Subscribe(threadMode = ThreadMode.MAIN) - public void onEvent(EventMessage msg) { - switch (msg.getType()) { - case EventMessage.FAVORITE_COMIC: - mFavoriteFragment.addItem((MiniComic) msg.getData()); - break; - case EventMessage.UN_FAVORITE_COMIC: - mFavoriteFragment.removeItem((Long) msg.getData()); - break; - case EventMessage.RESTORE_FAVORITE: - mFavoriteFragment.addItems((List) msg.getData()); - break; - } + public void checkUpdate() { + final List list = mComicManager.listFavorite(); + mCompositeSubscription.add(Manga.checkUpdate(mSourceManager, list) + .doOnNext(new Action1() { + @Override + public void call(Comic comic) { + if (comic != null) { + mComicManager.update(comic); + } + } + }) + .onBackpressureBuffer() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + private int count = 0; + + @Override + public void onCompleted() { + mBaseView.onComicCheckComplete(); + } + + @Override + public void onError(Throwable e) { + mBaseView.onComicCheckFail(); + } + + @Override + public void onNext(Comic comic) { + ++count; + MiniComic miniComic = comic == null ? null : new MiniComic(comic); + mBaseView.onComicCheckSuccess(miniComic, count, list.size()); + } + })); } } diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/HistoryPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/HistoryPresenter.java index c1eaaf02..be77b6e3 100644 --- a/app/src/main/java/com/hiroshi/cimoc/presenter/HistoryPresenter.java +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/HistoryPresenter.java @@ -1,51 +1,109 @@ package com.hiroshi.cimoc.presenter; -import android.content.Intent; - -import com.hiroshi.cimoc.core.ComicManager; -import com.hiroshi.cimoc.model.EventMessage; +import com.hiroshi.cimoc.manager.ComicManager; +import com.hiroshi.cimoc.model.Comic; import com.hiroshi.cimoc.model.MiniComic; -import com.hiroshi.cimoc.ui.activity.DetailActivity; -import com.hiroshi.cimoc.ui.fragment.HistoryFragment; - -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.rx.ToAnotherList; +import com.hiroshi.cimoc.ui.view.HistoryView; import java.util.List; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.functions.Func1; + /** * Created by Hiroshi on 2016/7/18. */ -public class HistoryPresenter extends BasePresenter { +public class HistoryPresenter extends BasePresenter { - private HistoryFragment mHistoryFragment; private ComicManager mComicManager; - public HistoryPresenter(HistoryFragment fragment) { - mHistoryFragment = fragment; - mComicManager = ComicManager.getInstance(); + @Override + protected void onViewAttach() { + mComicManager = ComicManager.getInstance(mBaseView); + } + + @SuppressWarnings("unchecked") + @Override + protected void initSubscription() { + super.initSubscription(); + addSubscription(RxEvent.EVENT_COMIC_READ, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.onItemUpdate((MiniComic) rxEvent.getData()); + } + }); + addSubscription(RxEvent.EVENT_COMIC_HISTORY_RESTORE, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.OnComicRestore((List) rxEvent.getData()); + } + }); + } + + public Comic load(long id) { + return mComicManager.load(id); } - public List getComic() { - return mComicManager.listHistory(); + public void load() { + mCompositeSubscription.add(mComicManager.listHistoryInRx() + .compose(new ToAnotherList<>(new Func1() { + @Override + public MiniComic call(Comic comic) { + return new MiniComic(comic); + } + })) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onComicLoadSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onComicLoadFail(); + } + })); } - public void onItemClick(int position) { - MiniComic comic = mHistoryFragment.getItem(position); - Intent intent = DetailActivity.createIntent(mHistoryFragment.getActivity(), comic.getId(), comic.getSource(), comic.getCid()); - mHistoryFragment.startActivity(intent); + public void delete(long id) { + Comic comic = mComicManager.load(id); + comic.setHistory(null); + mComicManager.updateOrDelete(comic); + mBaseView.onHistoryDelete(id); } - @Subscribe(threadMode = ThreadMode.MAIN) - public void onEvent(EventMessage msg) { - switch (msg.getType()) { - case EventMessage.HISTORY_COMIC: - mHistoryFragment.updateItem((MiniComic) msg.getData()); - break; - case EventMessage.DELETE_HISTORY: - mHistoryFragment.clearItem(); - break; - } + public void clear() { + mCompositeSubscription.add(mComicManager.listHistoryInRx() + .doOnNext(new Action1>() { + @Override + public void call(final List list) { + mComicManager.runInTx(new Runnable() { + @Override + public void run() { + for (Comic comic : list) { + comic.setHistory(null); + mComicManager.updateOrDelete(comic); + } + } + }); + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onHistoryClearSuccess(); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onExecuteFail(); + } + })); } } diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/LocalPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/LocalPresenter.java new file mode 100644 index 00000000..bcb4a019 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/LocalPresenter.java @@ -0,0 +1,176 @@ +package com.hiroshi.cimoc.presenter; + +import androidx.collection.LongSparseArray; +import android.util.Pair; + +import com.hiroshi.cimoc.core.Local; +import com.hiroshi.cimoc.manager.ComicManager; +import com.hiroshi.cimoc.manager.TaskManager; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.MiniComic; +import com.hiroshi.cimoc.model.Task; +import com.hiroshi.cimoc.rx.ToAnotherList; +import com.hiroshi.cimoc.saf.DocumentFile; +import com.hiroshi.cimoc.ui.view.LocalView; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; + +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +/** + * Created by Hiroshi on 2017/5/14. + */ + +public class LocalPresenter extends BasePresenter { + + private ComicManager mComicManager; + private TaskManager mTaskManager; + + @Override + protected void onViewAttach() { + mComicManager = ComicManager.getInstance(mBaseView); + mTaskManager = TaskManager.getInstance(mBaseView); + } + + // TODO 提取出来 + public Comic load(long id) { + return mComicManager.load(id); + } + + public void load() { + mCompositeSubscription.add(mComicManager.listLocalInRx() + .compose(new ToAnotherList<>(new Func1() { + @Override + public MiniComic call(Comic comic) { + return new MiniComic(comic); + } + })) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onComicLoadSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onComicLoadFail(); + } + })); + } + + private Pair, Set> buildHash() { + LongSparseArray> array = new LongSparseArray<>(); + Map map = new HashMap<>(); + Set set = new HashSet<>(); + for (Task task : mTaskManager.list()) { + List list = array.get(task.getKey()); + if (list == null) { + list = new ArrayList<>(); + array.put(task.getKey(), list); + } + list.add(task.getPath()); + } + for (Comic comic : mComicManager.listLocal()) { + map.put(comic.getCid(), comic); + set.addAll(array.get(comic.getId(), new ArrayList())); + } + return Pair.create(map, set); + } + + private List processScanResult(final List>> pairs) { + return mComicManager.callInTx(new Callable>() { + @Override + public List call() throws Exception { + Pair, Set> hash = buildHash(); + List result = new ArrayList<>(); + for (Pair> pr : pairs) { + Comic comic = hash.first.get(pr.first.getCid()); + if (comic != null) { + for (Task task : pr.second) { + task.setKey(comic.getId()); + if (!hash.second.contains(task.getPath())) { + mTaskManager.insert(task); + } + } + } else { + mComicManager.insert(pr.first); + for (Task task : pr.second) { + task.setKey(pr.first.getId()); + mTaskManager.insert(task); + } + result.add(pr.first); + } + } + return result; + } + }); + } + + public void scan(DocumentFile doc) { + mCompositeSubscription.add(Local.scan(doc) + .map(new Func1>>, List>() { + @Override + public List call(List>> pairs) { + return processScanResult(pairs); + } + }) + .compose(new ToAnotherList<>(new Func1() { + @Override + public MiniComic call(Comic comic) { + return new MiniComic(comic); + } + })) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onLocalScanSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onExecuteFail(); + } + })); + } + + public void deleteComic(long id) { + mCompositeSubscription.add(Observable.just(id) + .doOnNext(new Action1() { + @Override + public void call(final Long id) { + mComicManager.runInTx(new Runnable() { + @Override + public void run() { + mTaskManager.deleteByComicId(id); + mComicManager.deleteByKey(id); + } + }); + } + }).subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(Long id) { + mBaseView.onLocalDeleteSuccess(id); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onExecuteFail(); + } + })); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/MainPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/MainPresenter.java index 9bfd8739..b3b53eba 100644 --- a/app/src/main/java/com/hiroshi/cimoc/presenter/MainPresenter.java +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/MainPresenter.java @@ -1,117 +1,78 @@ package com.hiroshi.cimoc.presenter; -import android.app.Fragment; -import android.app.FragmentManager; -import android.view.MenuItem; +import com.hiroshi.cimoc.core.Update; +import com.hiroshi.cimoc.manager.ComicManager; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.MiniComic; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.ui.view.MainView; -import com.hiroshi.cimoc.R; -import com.hiroshi.cimoc.ui.activity.MainActivity; -import com.hiroshi.cimoc.ui.fragment.AboutFragment; -import com.hiroshi.cimoc.ui.fragment.CimocFragment; -import com.hiroshi.cimoc.ui.fragment.FavoriteFragment; -import com.hiroshi.cimoc.ui.fragment.HistoryFragment; -import com.hiroshi.cimoc.model.EventMessage; -import com.hiroshi.cimoc.ui.fragment.SettingsFragment; - -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; /** - * Created by Hiroshi on 2016/7/1. + * Created by Hiroshi on 2016/9/21. */ -public class MainPresenter extends BasePresenter { - private MainActivity mMainActivity; - private long mExitTime; +public class MainPresenter extends BasePresenter { - private int mCheckedItem; - private FragmentManager mFragmentManager; - private CimocFragment mCimocFragment; - private FavoriteFragment mFavoriteFragment; - private HistoryFragment mHistoryFragment; - private SettingsFragment mSettingsFragment; - private AboutFragment mAboutFragment; - private Fragment mCurrentFragment; - - public MainPresenter(MainActivity activity) { - mMainActivity = activity; - mExitTime = 0; - initFragment(); - } + private ComicManager mComicManager; - private void initFragment() { - mFragmentManager = mMainActivity.getFragmentManager(); - mCimocFragment = new CimocFragment(); - mFavoriteFragment = new FavoriteFragment(); - mHistoryFragment = new HistoryFragment(); - mSettingsFragment = new SettingsFragment(); - mAboutFragment = new AboutFragment(); - mFragmentManager.beginTransaction() - .add(R.id.main_fragment_container, mCimocFragment) - .add(R.id.main_fragment_container, mFavoriteFragment) - .add(R.id.main_fragment_container, mHistoryFragment) - .add(R.id.main_fragment_container, mSettingsFragment) - .add(R.id.main_fragment_container, mAboutFragment) - .hide(mFavoriteFragment) - .hide(mHistoryFragment) - .hide(mSettingsFragment) - .hide(mAboutFragment) - .commit(); - mCurrentFragment = mCimocFragment; - mCheckedItem = R.id.drawer_main; - mMainActivity.setCheckedItem(mCheckedItem); + @Override + protected void onViewAttach() { + mComicManager = ComicManager.getInstance(mBaseView); } - public void onBackPressed() { - if (mMainActivity.isDrawerOpen()) { - mMainActivity.closeDrawer(); - } else if (System.currentTimeMillis() - mExitTime > 2000) { - mMainActivity.showSnackbar("再按一次退出程序"); - mExitTime = System.currentTimeMillis(); - } else { - mMainActivity.finish(); - } + @Override + protected void initSubscription() { + addSubscription(RxEvent.EVENT_COMIC_READ, new Action1() { + @Override + public void call(RxEvent rxEvent) { + MiniComic comic = (MiniComic) rxEvent.getData(); + mBaseView.onLastChange(comic.getId(), comic.getSource(), comic.getCid(), + comic.getTitle(), comic.getCover()); + } + }); } - public void transFragment() { - switch (mCheckedItem) { - default: - case R.id.drawer_main: - mCurrentFragment = mCimocFragment; - break; - case R.id.drawer_comic: - mCurrentFragment = mFavoriteFragment; - break; - case R.id.drawer_history: - mCurrentFragment = mHistoryFragment; - break; - case R.id.drawer_settings: - mCurrentFragment = mSettingsFragment; - break; - case R.id.drawer_about: - mCurrentFragment = mAboutFragment; - break; - } - mFragmentManager.beginTransaction().show(mCurrentFragment).commit(); - mMainActivity.hideProgressBar(); + public boolean checkLocal(long id) { + Comic comic = mComicManager.load(id); + return comic != null && comic.getLocal(); } - public boolean onNavigationItemSelected(MenuItem menuItem) { - if (menuItem.getItemId() == mCheckedItem) { - return false; - } - mCheckedItem = menuItem.getItemId(); - mMainActivity.showProgressBar(); - mFragmentManager.beginTransaction().hide(mCurrentFragment).commit(); - mMainActivity.setToolbarTitle(menuItem.getTitle().toString()); - mMainActivity.setCheckedItem(mCheckedItem); - mMainActivity.closeDrawer(); - return true; + public void loadLast() { + mCompositeSubscription.add(mComicManager.loadLast() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(Comic comic) { + if (comic != null) { + mBaseView.onLastLoadSuccess(comic.getId(), comic.getSource(), comic.getCid(), comic.getTitle(), comic.getCover()); + } + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onLastLoadFail(); + } + })); } - @Subscribe(threadMode = ThreadMode.MAIN) - public void onEvent(EventMessage msg) { - + public void checkUpdate(final String version) { + mCompositeSubscription.add(Update.check() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(String s) { + if (-1 == version.indexOf(s) && -1 == version.indexOf("t")) { + mBaseView.onUpdateReady(); + } + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + } + })); } } diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/PartFavoritePresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/PartFavoritePresenter.java new file mode 100644 index 00000000..a692aa0c --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/PartFavoritePresenter.java @@ -0,0 +1,166 @@ +package com.hiroshi.cimoc.presenter; + +import androidx.collection.LongSparseArray; + +import com.hiroshi.cimoc.manager.ComicManager; +import com.hiroshi.cimoc.manager.TagManager; +import com.hiroshi.cimoc.manager.TagRefManager; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.MiniComic; +import com.hiroshi.cimoc.model.TagRef; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.rx.ToAnotherList; +import com.hiroshi.cimoc.ui.view.PartFavoriteView; + +import java.util.ArrayList; +import java.util.List; + +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.functions.Func1; + +/** + * Created by Hiroshi on 2016/10/11. + */ + +public class PartFavoritePresenter extends BasePresenter { + + private ComicManager mComicManager; + private TagRefManager mTagRefManager; + private long mTagId; + private LongSparseArray mSavedComic; + + @Override + protected void onViewAttach() { + mComicManager = ComicManager.getInstance(mBaseView); + mTagRefManager = TagRefManager.getInstance(mBaseView); + mSavedComic = new LongSparseArray<>(); + } + + @SuppressWarnings("unchecked") + @Override + protected void initSubscription() { + addSubscription(RxEvent.EVENT_COMIC_UNFAVORITE, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.onComicRemove((long) rxEvent.getData()); + } + }); + addSubscription(RxEvent.EVENT_TAG_UPDATE, new Action1() { + @Override + public void call(RxEvent rxEvent) { + long id = (long) rxEvent.getData(); + List list = (List) rxEvent.getData(1); + if (list.contains(mTagId)) { + MiniComic comic = new MiniComic(mComicManager.load(id)); + mBaseView.onComicAdd(comic); + } else { + mBaseView.onComicRemove(id); + } + } + }); + addSubscription(RxEvent.EVENT_COMIC_CANCEL_HIGHLIGHT, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.onHighlightCancel((MiniComic) rxEvent.getData()); + } + }); + addSubscription(RxEvent.EVENT_COMIC_READ, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.onComicRead((MiniComic) rxEvent.getData()); + } + }); + } + + private Observable> getObservable(long id) { + if (id == TagManager.TAG_CONTINUE) { + return mComicManager.listContinueInRx(); + } else if (id == TagManager.TAG_FINISH) { + return mComicManager.listFinishInRx(); + } else { + return mComicManager.listFavoriteByTag(id); + } + } + + public void load(long id) { + mTagId = id; + mCompositeSubscription.add(getObservable(id) + .compose(new ToAnotherList<>(new Func1() { + @Override + public MiniComic call(Comic comic) { + return new MiniComic(comic); + } + })) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onComicLoadSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onComicLoadFail(); + } + })); + } + + private List buildIdList(List list) { + List result = new ArrayList<>(list.size()); + for (MiniComic comic : list) { + result.add(comic.getId()); + } + return result; + } + + public void loadComicTitle(List list) { + // TODO 不使用 in + mCompositeSubscription.add(mComicManager.listFavoriteNotIn(buildIdList(list)) + .compose(new ToAnotherList<>(new Func1() { + @Override + public String call(Comic comic) { + mSavedComic.put(comic.getId(), comic); + return comic.getTitle(); + } + })) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onComicTitleLoadSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onComicTitleLoadFail(); + } + })); + } + + public void insert(boolean[] check) { + // Todo 异步 + if (check != null && mSavedComic != null && check.length == mSavedComic.size()) { + List rList = new ArrayList<>(); + List cList = new ArrayList<>(); + for (int i = 0; i != check.length; ++i) { + if (check[i]) { + MiniComic comic = new MiniComic(mSavedComic.valueAt(i)); + rList.add(new TagRef(null, mTagId, comic.getId())); + cList.add(comic); + } + } + mTagRefManager.insertInTx(rList); + mBaseView.onComicInsertSuccess(cList); + } else { + mBaseView.onComicInsertFail(); + } + mSavedComic.clear(); + } + + public void delete(long id) { + mTagRefManager.delete(mTagId, id); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/ReaderPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/ReaderPresenter.java index 9a6538d9..34ebe448 100644 --- a/app/src/main/java/com/hiroshi/cimoc/presenter/ReaderPresenter.java +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/ReaderPresenter.java @@ -1,127 +1,282 @@ package com.hiroshi.cimoc.presenter; -import com.hiroshi.cimoc.core.ComicManager; -import com.hiroshi.cimoc.core.Kami; -import com.hiroshi.cimoc.core.base.Manga; -import com.hiroshi.cimoc.model.Chapter; -import com.hiroshi.cimoc.model.EventMessage; -import com.hiroshi.cimoc.ui.activity.ReaderActivity; -import com.hiroshi.cimoc.ui.adapter.PicturePagerAdapter; -import com.hiroshi.cimoc.ui.adapter.PreloadAdapter; +import android.net.Uri; +import android.os.Build; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; +import com.hiroshi.cimoc.core.Download; +import com.hiroshi.cimoc.core.Local; +import com.hiroshi.cimoc.core.Manga; +import com.hiroshi.cimoc.core.Storage; +import com.hiroshi.cimoc.manager.ComicManager; +import com.hiroshi.cimoc.manager.SourceManager; +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.rx.RxBus; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.saf.DocumentFile; +import com.hiroshi.cimoc.ui.view.ReaderView; +import com.hiroshi.cimoc.utils.StringUtils; -import java.util.ArrayList; -import java.util.Collections; +import java.io.File; +import java.io.InputStream; import java.util.List; +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; + /** * Created by Hiroshi on 2016/7/8. */ -public class ReaderPresenter extends BasePresenter { +public class ReaderPresenter extends BasePresenter { private final static int LOAD_NULL = 0; - private final static int LOAD_PREV = 1; - private final static int LOAD_NEXT = 2; + private final static int LOAD_INIT = 1; + private final static int LOAD_PREV = 2; + private final static int LOAD_NEXT = 3; - private ReaderActivity mReaderActivity; - private PreloadAdapter mPreloadAdapter; + private ChapterManger mChapterManger; private ComicManager mComicManager; - private Manga mManga; + private SourceManager mSourceManager; + private Comic mComic; - private int status; - private boolean load; + private boolean isShowNext = true; + private boolean isShowPrev = true; + private int count = 0; + private int status = LOAD_INIT; - public ReaderPresenter(ReaderActivity activity, int position) { - mReaderActivity = activity; - mComicManager = ComicManager.getInstance(); - List list = new ArrayList<>(mComicManager.getChapters()); - Collections.reverse(list); - mPreloadAdapter = new PreloadAdapter(list.toArray(new Chapter[list.size()]), list.size() - position - 1, PicturePagerAdapter.MAX_COUNT / 2 + 1); - mManga = Kami.getMangaById(mComicManager.getSource()); + @Override + protected void onViewAttach() { + mComicManager = ComicManager.getInstance(mBaseView); + mSourceManager = SourceManager.getInstance(mBaseView); } @Override - public void onCreate() { - super.onCreate(); - load = false; - status = LOAD_NEXT; - mManga.browse(mComicManager.getCid(), mPreloadAdapter.getNextChapter().getPath()); + protected void initSubscription() { + addSubscription(RxEvent.EVENT_PICTURE_PAGING, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.onPicturePaging((ImageUrl) rxEvent.getData()); + } + }); } - @Override - public void onDestroy() { - super.onDestroy(); - mManga.cancel(); + public void lazyLoad(final ImageUrl imageUrl) { + mCompositeSubscription.add(Manga.loadLazyUrl(mSourceManager.getParser(mComic.getSource()), imageUrl.getUrl()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(String url) { + if (url == null) { + mBaseView.onImageLoadFail(imageUrl.getId()); + } else { + mBaseView.onImageLoadSuccess(imageUrl.getId(), url); + } + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onImageLoadFail(imageUrl.getId()); + } + })); } - public void afterRead(int progress) { - if (load) { - mComicManager.afterRead(progress); + public void loadInit(long id, Chapter[] array) { + mComic = mComicManager.load(id); + for (int i = 0; i != array.length; ++i) { + if (array[i].getPath().equals(mComic.getLast())) { + this.mChapterManger = new ChapterManger(array, i); + images(getObservable(array[i])); + } + } + } + + public void loadNext() { + if (status == LOAD_NULL && isShowNext) { + Chapter chapter = mChapterManger.getNextChapter(); + if (chapter != null) { + status = LOAD_NEXT; + images(getObservable(chapter)); + mBaseView.onNextLoading(); + } else { + isShowNext = false; + mBaseView.onNextLoadNone(); + } } } - public void onPageStateIdle(boolean isFirst) { - if (status == LOAD_NULL) { - Chapter chapter = isFirst ? mPreloadAdapter.getPrevChapter() : mPreloadAdapter.getNextChapter(); + public void loadPrev() { + if (status == LOAD_NULL && isShowPrev) { + Chapter chapter = mChapterManger.getPrevChapter(); if (chapter != null) { - status = isFirst ? LOAD_PREV : LOAD_NEXT; - mManga.browse(mComicManager.getCid(), chapter.getPath()); + status = LOAD_PREV; + images(getObservable(chapter)); + mBaseView.onPrevLoading(); } else { - mReaderActivity.notifySpecialPage(isFirst, PicturePagerAdapter.STATUS_NULL); + isShowPrev = false; + mBaseView.onPrevLoadNone(); } } } - public void onPageSelected(int position) { - boolean flag = mPreloadAdapter.moveToPosition(position); - Chapter chapter = mPreloadAdapter.getValidChapter(); - if (chapter == null) { - mReaderActivity.hideChapterInfo(); - } else if (flag) { - switchChapter(); - } else { - mReaderActivity.setReadProgress(mPreloadAdapter.getValidProgress()); + private Observable> getObservable(Chapter chapter) { + if (mComic.getLocal()) { + DocumentFile dir = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? + DocumentFile.fromSubTreeUri(mBaseView.getAppInstance(), Uri.parse(chapter.getPath())) : + DocumentFile.fromFile(new File(Uri.parse(chapter.getPath()).getPath())); + return Local.images(dir, chapter); } + return chapter.isComplete() ? Download.images(mBaseView.getAppInstance().getDocumentFile(), + mComic, chapter, mSourceManager.getParser(mComic.getSource()).getTitle()) : + Manga.getChapterImage(mComic, mSourceManager.getParser(mComic.getSource()), mComic.getCid(), chapter.getPath()); + } + + public void toNextChapter() { + Chapter chapter = mChapterManger.nextChapter(); + if (chapter != null) { + updateChapter(chapter, true); + } + } + + public void toPrevChapter() { + Chapter chapter = mChapterManger.prevChapter(); + if (chapter != null) { + updateChapter(chapter, false); + } + } + + private void updateChapter(Chapter chapter, boolean isNext) { + mBaseView.onChapterChange(chapter); + mComic.setLast(chapter.getPath()); + mComic.setChapter(chapter.getTitle()); + mComic.setPage(isNext ? 1 : chapter.getCount()); + mComicManager.update(mComic); + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_COMIC_UPDATE, mComic.getId())); + } + + public void savePicture(InputStream inputStream, String url, String title, int page) { + mCompositeSubscription.add(Storage.savePicture(mBaseView.getAppInstance().getContentResolver(), + mBaseView.getAppInstance().getDocumentFile(), inputStream, buildPictureName(title, page, url)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(Uri uri) { + mBaseView.onPictureSaveSuccess(uri); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + throwable.printStackTrace(); + mBaseView.onPictureSaveFail(); + } + })); + } + + private String buildPictureName(String title, int page, String url) { + String suffix = StringUtils.split(url, "\\.", -1); + suffix = suffix == null ? "jpg" : suffix.split("\\?")[0]; + return StringUtils.format("%s_%s_%03d.%s", StringUtils.filter(mComic.getTitle()), StringUtils.filter(title), page, suffix); } - public int getSource() { - return mComicManager.getSource(); - } - - public int getOffset() { - return mPreloadAdapter.getCurrentOffset(); - } - - private void switchChapter() { - mReaderActivity.updateChapterInfo(mPreloadAdapter.getValidProgress(), mPreloadAdapter.getMax(), mPreloadAdapter.getValidChapter().getTitle()); - mComicManager.setLast(mPreloadAdapter.getValidChapter().getPath()); - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onEvent(EventMessage msg) { - String[] array; - switch (msg.getType()) { - case EventMessage.PARSE_PIC_SUCCESS: - array = (String[]) msg.getData(); - if (status == LOAD_PREV) { - mReaderActivity.setPrevImage(array); - mPreloadAdapter.movePrev(array.length); - } else { - mReaderActivity.setNextImage(array); - mPreloadAdapter.moveNext(array.length); - } - switchChapter(); - mReaderActivity.setNoneLimit(); - load = true; - status = LOAD_NULL; - break; - case EventMessage.PARSE_PIC_FAIL: - case EventMessage.NETWORK_ERROR: - mReaderActivity.notifySpecialPage(status == LOAD_PREV, PicturePagerAdapter.STATUS_ERROR); - break; + public void updateComic(int page) { + if (status != LOAD_INIT) { + mComic.setPage(page); + mComicManager.update(mComic); + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_COMIC_UPDATE, mComic.getId())); } } + public void switchNight() { + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_SWITCH_NIGHT)); + } + + private void images(Observable> observable) { + mCompositeSubscription.add(observable + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + Chapter chapter; + switch (status) { + case LOAD_INIT: + chapter = mChapterManger.moveNext(); + chapter.setCount(list.size()); + if (!chapter.getTitle().equals(mComic.getTitle())) { + mComic.setChapter(chapter.getTitle()); + mComicManager.update(mComic); + } + mBaseView.onChapterChange(chapter); + mBaseView.onInitLoadSuccess(list, mComic.getPage(), mComic.getSource(), mComic.getLocal()); + break; + case LOAD_PREV: + chapter = mChapterManger.movePrev(); + chapter.setCount(list.size()); + mBaseView.onPrevLoadSuccess(list); + break; + case LOAD_NEXT: + chapter = mChapterManger.moveNext(); + chapter.setCount(list.size()); + mBaseView.onNextLoadSuccess(list); + break; + } + status = LOAD_NULL; + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onParseError(); + if (status != LOAD_INIT && ++count < 2) { + status = LOAD_NULL; + } + } + })); + } + + private class ChapterManger { + + private Chapter[] array; + private int index; + private int prev; + private int next; + + ChapterManger(Chapter[] array, int index) { + this.array = array; + this.index = index; + prev = index + 1; + next = index; + } + + Chapter getPrevChapter() { + return prev < array.length ? array[prev] : null; + } + + Chapter getNextChapter() { + return next >= 0 ? array[next] : null; + } + + Chapter prevChapter() { + if (index + 1 < prev) { + return array[++index]; + } + return null; + } + + Chapter nextChapter() { + if (index - 1 > next) { + return array[--index]; + } + return null; + } + + Chapter movePrev() { + return array[prev++]; + } + + Chapter moveNext() { + return array[next--]; + } + + } + } diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/ResultPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/ResultPresenter.java index 5d0755f5..73b068d8 100644 --- a/app/src/main/java/com/hiroshi/cimoc/presenter/ResultPresenter.java +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/ResultPresenter.java @@ -1,88 +1,153 @@ package com.hiroshi.cimoc.presenter; -import android.content.Intent; - -import com.hiroshi.cimoc.core.Kami; -import com.hiroshi.cimoc.core.base.Manga; +import com.hiroshi.cimoc.core.Manga; +import com.hiroshi.cimoc.manager.SourceManager; import com.hiroshi.cimoc.model.Comic; -import com.hiroshi.cimoc.ui.activity.DetailActivity; -import com.hiroshi.cimoc.ui.activity.ResultActivity; -import com.hiroshi.cimoc.model.EventMessage; - -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.Parser; +import com.hiroshi.cimoc.ui.view.ResultView; import java.util.List; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action0; +import rx.functions.Action1; + /** * Created by Hiroshi on 2016/7/4. */ -public class ResultPresenter extends BasePresenter { +public class ResultPresenter extends BasePresenter { - private ResultActivity mResultActivity; - - private Manga mManga; + private static final int STATE_NULL = 0; + private static final int STATE_DOING = 1; + private static final int STATE_DONE = 3; + private SourceManager mSourceManager; + private State[] mStateArray; private String keyword; - private int page; - private boolean isLoading; + private boolean strictSearch; + private int error = 0; + private String keywordTemp; + private String comicTitleTemp = ""; - public ResultPresenter(ResultActivity activity, int source, String keyword) { - this.mResultActivity = activity; - this.mManga = Kami.getMangaById(source); + public ResultPresenter(int[] source, String keyword, boolean strictSearch) { this.keyword = keyword; - this.page = 0; - this.isLoading = false; + this.strictSearch = strictSearch; + if (source != null) { + initStateArray(source); + } } @Override - public void onCreate() { - super.onCreate(); - mManga.search(keyword, ++page); + protected void onViewAttach() { + mSourceManager = SourceManager.getInstance(mBaseView); + if (mStateArray == null) { + initStateArray(loadSource()); + } } - @Override - public void onDestroy() { - super.onDestroy(); - mManga.cancel(); + private void initStateArray(int[] source) { + mStateArray = new State[source.length]; + for (int i = 0; i != mStateArray.length; ++i) { + mStateArray[i] = new State(); + mStateArray[i].source = source[i]; + mStateArray[i].page = 0; + mStateArray[i].state = STATE_NULL; + } } - public void onScrolled(int dy) { - int lastItem = mResultActivity.findLastItemPosition(); - int itemCount = mResultActivity.getItemCount(); - if (lastItem >= itemCount - 4 && dy > 0) { - if (!isLoading) { - isLoading = true; - mManga.search(keyword, ++page); - } + private int[] loadSource() { + List list = mSourceManager.listEnable(); + int[] source = new int[list.size()]; + for (int i = 0; i != source.length; ++i) { + source[i] = list.get(i).getType(); } + return source; } - public void onItemClick(int position) { - Comic comic = mResultActivity.getItem(position); - Intent intent = DetailActivity.createIntent(mResultActivity, comic.getId(), comic.getSource(), comic.getCid()); - mResultActivity.startActivity(intent); - } + public void loadCategory() { + if (mStateArray[0].state == STATE_NULL) { + Parser parser = mSourceManager.getParser(mStateArray[0].source); + mStateArray[0].state = STATE_DOING; - @Subscribe(threadMode = ThreadMode.MAIN) - public void onEvent(EventMessage msg) { - switch (msg.getType()) { - case EventMessage.SEARCH_SUCCESS: - mResultActivity.hideProgressBar(); - mResultActivity.addAll((List) msg.getData()); - isLoading = false; - break; - case EventMessage.SEARCH_FAIL: - if (page == 1) { - mResultActivity.hideProgressBar(); - mResultActivity.showSnackbar("搜索结果为空"); + //修复扑飞漫画分类查看 + if (mStateArray[0].page == 0) { + if (parser.getTitle().equals("扑飞漫画")) { + keywordTemp = keyword; + keyword = keyword.replace("_%d", ""); + } + } else { + if (parser.getTitle().equals("扑飞漫画")) { + keyword = keywordTemp; } - break; - case EventMessage.NETWORK_ERROR: - mResultActivity.hideProgressBar(); - mResultActivity.showSnackbar("网络错误"); - isLoading = false; - break; + } + mCompositeSubscription.add(Manga.getCategoryComic(parser, keyword, ++mStateArray[0].page) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + + //修复扑飞漫画分类查看时的重复加载列表问题 + if (!comicTitleTemp.equals("") && comicTitleTemp.equals(list.get(0).getTitle())) { + list.clear(); + } + comicTitleTemp = list.get(0).getTitle(); + + mBaseView.onLoadSuccess(list); + mStateArray[0].state = STATE_NULL; + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + throwable.printStackTrace(); + if (mStateArray[0].page == 1) { + mBaseView.onLoadFail(); + } + } + })); + } + } + + public void loadSearch() { + if (mStateArray.length == 0) { + mBaseView.onSearchError(); + return; + } + for (final State obj : mStateArray) { + if (obj.state == STATE_NULL) { + Parser parser = mSourceManager.getParser(obj.source); + obj.state = STATE_DOING; + mCompositeSubscription.add(Manga.getSearchResult(parser, keyword, ++obj.page, strictSearch) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(Comic comic) { + mBaseView.onSearchSuccess(comic); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + throwable.printStackTrace(); + if (obj.page == 1) { + obj.state = STATE_DONE; + if (++error == mStateArray.length) { + mBaseView.onSearchError(); + } + } + } + }, new Action0() { + @Override + public void call() { + obj.state = STATE_NULL; + } + })); + } } } + private static class State { + int source; + int page; + int state; + } + } diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/SearchPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/SearchPresenter.java new file mode 100644 index 00000000..02973230 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/SearchPresenter.java @@ -0,0 +1,58 @@ +package com.hiroshi.cimoc.presenter; + +import com.hiroshi.cimoc.core.Manga; +import com.hiroshi.cimoc.manager.SourceManager; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.ui.view.SearchView; + +import java.util.List; + +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; + +/** + * Created by Hiroshi on 2016/10/11. + */ + +public class SearchPresenter extends BasePresenter { + + private SourceManager mSourceManager; + + @Override + protected void onViewAttach() { + mSourceManager = SourceManager.getInstance(mBaseView); + } + + public void loadSource() { + mCompositeSubscription.add(mSourceManager.listEnableInRx() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onSourceLoadSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onSourceLoadFail(); + } + })); + } + + public void loadAutoComplete(String keyword) { + mCompositeSubscription.add(Manga.loadAutoComplete(keyword) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onAutoCompleteLoadSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + throwable.printStackTrace(); + } + })); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/SettingsPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/SettingsPresenter.java index eae2fdb0..26f4e8a9 100644 --- a/app/src/main/java/com/hiroshi/cimoc/presenter/SettingsPresenter.java +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/SettingsPresenter.java @@ -1,98 +1,110 @@ package com.hiroshi.cimoc.presenter; -import android.content.DialogInterface; -import android.support.v7.app.AlertDialog; +import android.util.Pair; -import com.hiroshi.cimoc.R; -import com.hiroshi.cimoc.core.ComicManager; +import com.facebook.drawee.backends.pipeline.Fresco; +import com.hiroshi.cimoc.core.Download; +import com.hiroshi.cimoc.core.Storage; +import com.hiroshi.cimoc.manager.ComicManager; +import com.hiroshi.cimoc.manager.TaskManager; import com.hiroshi.cimoc.model.Comic; -import com.hiroshi.cimoc.model.EventMessage; -import com.hiroshi.cimoc.ui.fragment.SettingsFragment; -import com.hiroshi.cimoc.utils.BackupUtils; -import com.hiroshi.cimoc.utils.FileUtils; - -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; +import com.hiroshi.cimoc.model.MiniComic; +import com.hiroshi.cimoc.model.Task; +import com.hiroshi.cimoc.rx.RxBus; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.saf.DocumentFile; +import com.hiroshi.cimoc.ui.view.SettingsView; import java.util.List; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action0; +import rx.functions.Action1; + /** * Created by Hiroshi on 2016/7/22. */ -public class SettingsPresenter extends BasePresenter { +public class SettingsPresenter extends BasePresenter { - private SettingsFragment mSettingsFragment; private ComicManager mComicManager; + private TaskManager mTaskManager; - public SettingsPresenter(SettingsFragment fragment) { - mSettingsFragment = fragment; - mComicManager = ComicManager.getInstance(); + @Override + protected void onViewAttach() { + mComicManager = ComicManager.getInstance(mBaseView); + mTaskManager = TaskManager.getInstance(mBaseView); } - public void cleanCache() { - mSettingsFragment.showAlertDialog("正在删除.."); - FileUtils.deleteDir(mSettingsFragment.getActivity().getCacheDir()); - mSettingsFragment.showSnackbar("删除成功"); - mSettingsFragment.hideAlertDialog(); + public void clearCache() { + Fresco.getImagePipeline().clearDiskCaches(); } - public void backupComic() { - mSettingsFragment.showAlertDialog("正在备份.."); - List list = mComicManager.listBackup(); - if (BackupUtils.saveComic(list)) { - mSettingsFragment.showSnackbar("备份成功 共 " + list.size() + " 条记录"); - } else { - mSettingsFragment.showSnackbar("备份失败 共 " + list.size() + " 条记录"); - } - mSettingsFragment.hideAlertDialog(); + public void moveFiles(DocumentFile dst) { + mCompositeSubscription.add(Storage.moveRootDir(mBaseView.getAppInstance().getContentResolver(), + mBaseView.getAppInstance().getDocumentFile(), dst) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(String msg) { + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_DIALOG_PROGRESS, msg)); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onExecuteFail(); + } + }, new Action0() { + @Override + public void call() { + mBaseView.onFileMoveSuccess(); + } + })); } - private int choice; - - public void restoreComic() { - final String[] files = BackupUtils.showBackupFiles(); - if (files == null || files.length == 0) { - mSettingsFragment.showSnackbar("没有找到备份文件"); - return; + private void updateKey(long key, List list) { + for (Task task : list) { + task.setKey(key); } - AlertDialog.Builder builder = new AlertDialog.Builder(mSettingsFragment.getActivity(), R.style.AppTheme_Dialog_Alert); - builder.setTitle("选择文件"); - builder.setSingleChoiceItems(files, -1, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - choice = which; - } - }); - builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mSettingsFragment.showAlertDialog("正在恢复.."); - List list = BackupUtils.restoreComic(files[choice]); - mComicManager.restoreFavorite(list); - } - }); - builder.show(); } - public void cleanHistory() { - mSettingsFragment.showAlertDialog("正在删除.."); - mComicManager.cleanHistory(); - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onEvent(EventMessage msg) { - switch (msg.getType()) { - case EventMessage.DELETE_HISTORY: - int count = (int) msg.getData(); - mSettingsFragment.hideAlertDialog(); - mSettingsFragment.showSnackbar("删除成功 共 " + count + " 条记录"); - break; - case EventMessage.RESTORE_FAVORITE: - List list = (List) msg.getData(); - mSettingsFragment.hideAlertDialog(); - mSettingsFragment.showSnackbar("恢复成功 共 " + list.size() + " 条记录"); - break; - } + public void scanTask() { + // Todo 重写一下 + mCompositeSubscription.add(Download.scan(mBaseView.getAppInstance().getContentResolver(), mBaseView.getAppInstance().getDocumentFile()) + .doOnNext(new Action1>>() { + @Override + public void call(Pair> pair) { + Comic comic = mComicManager.load(pair.first.getSource(), pair.first.getCid()); + if (comic == null) { + mComicManager.insert(pair.first); + updateKey(pair.first.getId(), pair.second); + mTaskManager.insertInTx(pair.second); + comic = pair.first; + } else { + comic.setDownload(System.currentTimeMillis()); + mComicManager.update(comic); + updateKey(comic.getId(), pair.second); + mTaskManager.insertIfNotExist(pair.second); + } + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_TASK_INSERT, new MiniComic(comic))); + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_DIALOG_PROGRESS, comic.getTitle())); + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>>() { + @Override + public void call(Pair> pair) { + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onExecuteFail(); + } + }, new Action0() { + @Override + public void call() { + mBaseView.onExecuteSuccess(); + } + })); } } diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/SourceDetailPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/SourceDetailPresenter.java new file mode 100644 index 00000000..648e0f5e --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/SourceDetailPresenter.java @@ -0,0 +1,29 @@ +package com.hiroshi.cimoc.presenter; + +import com.hiroshi.cimoc.manager.ComicManager; +import com.hiroshi.cimoc.manager.SourceManager; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.ui.view.SourceDetailView; + +/** + * Created by Hiroshi on 2017/1/18. + */ + +public class SourceDetailPresenter extends BasePresenter { + + private SourceManager mSourceManager; + private ComicManager mComicManager; + + @Override + protected void onViewAttach() { + mSourceManager = SourceManager.getInstance(mBaseView); + mComicManager = ComicManager.getInstance(mBaseView); + } + + public void load(int type) { + Source source = mSourceManager.load(type); + long count = mComicManager.countBySource(type); + mBaseView.onSourceLoadSuccess(type, source.getTitle(), count); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/SourcePresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/SourcePresenter.java new file mode 100644 index 00000000..4e02878a --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/SourcePresenter.java @@ -0,0 +1,44 @@ +package com.hiroshi.cimoc.presenter; + +import com.hiroshi.cimoc.manager.SourceManager; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.ui.view.SourceView; + +import java.util.List; + +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; + +/** + * Created by Hiroshi on 2016/8/11. + */ +public class SourcePresenter extends BasePresenter { + + private SourceManager mSourceManager; + + @Override + protected void onViewAttach() { + mSourceManager = SourceManager.getInstance(mBaseView); + } + + public void load() { + mCompositeSubscription.add(mSourceManager.list() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onSourceLoadSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onSourceLoadFail(); + } + })); + } + + public void update(Source source) { + mSourceManager.update(source); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/TagEditorPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/TagEditorPresenter.java new file mode 100644 index 00000000..270d0e4a --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/TagEditorPresenter.java @@ -0,0 +1,114 @@ +package com.hiroshi.cimoc.presenter; + +import com.hiroshi.cimoc.manager.TagManager; +import com.hiroshi.cimoc.manager.TagRefManager; +import com.hiroshi.cimoc.misc.Switcher; +import com.hiroshi.cimoc.model.Tag; +import com.hiroshi.cimoc.model.TagRef; +import com.hiroshi.cimoc.rx.RxBus; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.rx.ToAnotherList; +import com.hiroshi.cimoc.ui.view.TagEditorView; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +/** + * Created by Hiroshi on 2016/12/2. + */ + +public class TagEditorPresenter extends BasePresenter { + + private TagManager mTagManager; + private TagRefManager mTagRefManager; + private long mComicId; + private Set mTagSet; + + @Override + protected void onViewAttach() { + mTagManager = TagManager.getInstance(mBaseView); + mTagRefManager = TagRefManager.getInstance(mBaseView); + mTagSet = new HashSet<>(); + } + + public void load(long id) { + mComicId = id; + mCompositeSubscription.add(mTagManager.listInRx() + .doOnNext(new Action1>() { + @Override + public void call(List list) { + for (TagRef ref : mTagRefManager.listByComic(mComicId)) { + mTagSet.add(ref.getTid()); + } + } + }) + .compose(new ToAnotherList<>(new Func1>() { + @Override + public Switcher call(Tag tag) { + return new Switcher<>(tag, mTagSet.contains(tag.getId())); + } + })) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>>() { + @Override + public void call(List> list) { + mBaseView.onTagLoadSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onTagLoadFail(); + } + })); + } + + private void updateInTx(final List list) { + mTagRefManager.runInTx(new Runnable() { + @Override + public void run() { + for (long id : list) { + if (!mTagSet.contains(id)) { + mTagRefManager.insert(new TagRef(null, id, mComicId)); + } + } + mTagSet.removeAll(list); + for (long id : mTagSet) { + mTagRefManager.delete(id, mComicId); + } + } + }); + } + + public void updateRef(List list) { + mCompositeSubscription.add(Observable.just(list) + .doOnNext(new Action1>() { + @Override + public void call(List list) { + updateInTx(list); + mTagSet.clear(); + mTagSet.addAll(list); + } + }).subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onTagUpdateSuccess(); + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_TAG_UPDATE, mComicId, list)); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onTagUpdateFail(); + } + })); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/TagPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/TagPresenter.java new file mode 100644 index 00000000..7b1526b5 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/TagPresenter.java @@ -0,0 +1,81 @@ +package com.hiroshi.cimoc.presenter; + +import com.hiroshi.cimoc.manager.TagManager; +import com.hiroshi.cimoc.manager.TagRefManager; +import com.hiroshi.cimoc.model.Tag; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.ui.view.TagView; + +import java.util.List; + +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; + +/** + * Created by Hiroshi on 2016/10/10. + */ + +public class TagPresenter extends BasePresenter { + + private TagManager mTagManager; + private TagRefManager mTagRefManager; + + @Override + protected void onViewAttach() { + mTagManager = TagManager.getInstance(mBaseView); + mTagRefManager = TagRefManager.getInstance(mBaseView); + } + + @SuppressWarnings("unchecked") + @Override + protected void initSubscription() { + addSubscription(RxEvent.EVENT_TAG_RESTORE, new Action1() { + @Override + public void call(RxEvent rxEvent) { + mBaseView.onTagRestore((List) rxEvent.getData()); + } + }); + } + + public void load() { + mCompositeSubscription.add(mTagManager.listInRx() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onTagLoadSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onTagLoadFail(); + } + })); + } + + public void insert(Tag tag) { + mTagManager.insert(tag); + } + + public void delete(final Tag tag) { + mCompositeSubscription.add(mTagRefManager.runInRx(new Runnable() { + @Override + public void run() { + mTagRefManager.deleteByTag(tag.getId()); + mTagManager.delete(tag); + } + }).observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(Void aVoid) { + mBaseView.onTagDeleteSuccess(tag); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onTagDeleteFail(); + } + })); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/presenter/TaskPresenter.java b/app/src/main/java/com/hiroshi/cimoc/presenter/TaskPresenter.java new file mode 100644 index 00000000..821123fe --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/presenter/TaskPresenter.java @@ -0,0 +1,223 @@ +package com.hiroshi.cimoc.presenter; + +import com.hiroshi.cimoc.core.Download; +import com.hiroshi.cimoc.manager.ComicManager; +import com.hiroshi.cimoc.manager.SourceManager; +import com.hiroshi.cimoc.manager.TaskManager; +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.MiniComic; +import com.hiroshi.cimoc.model.Task; +import com.hiroshi.cimoc.rx.RxBus; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.rx.ToAnotherList; +import com.hiroshi.cimoc.ui.view.TaskView; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +/** + * Created by Hiroshi on 2016/9/7. + */ +public class TaskPresenter extends BasePresenter { + + private TaskManager mTaskManager; + private ComicManager mComicManager; + private SourceManager mSourceManager; + private Comic mComic; + + @Override + protected void onViewAttach() { + mTaskManager = TaskManager.getInstance(mBaseView); + mComicManager = ComicManager.getInstance(mBaseView); + mSourceManager = SourceManager.getInstance(mBaseView); + } + + @SuppressWarnings("unchecked") + @Override + protected void initSubscription() { + addSubscription(RxEvent.EVENT_TASK_STATE_CHANGE, new Action1() { + @Override + public void call(RxEvent rxEvent) { + long id = (long) rxEvent.getData(1); + switch ((int) rxEvent.getData()) { + case Task.STATE_PARSE: + mBaseView.onTaskParse(id); + break; + case Task.STATE_ERROR: + mBaseView.onTaskError(id); + break; + case Task.STATE_PAUSE: + mBaseView.onTaskPause(id); + break; + } + } + }); + addSubscription(RxEvent.EVENT_TASK_PROCESS, new Action1() { + @Override + public void call(RxEvent rxEvent) { + long id = (long) rxEvent.getData(); + mBaseView.onTaskProcess(id, (int) rxEvent.getData(1), (int) rxEvent.getData(2)); + } + }); + addSubscription(RxEvent.EVENT_TASK_INSERT, new Action1() { + @Override + public void call(RxEvent rxEvent) { + List list = (List) rxEvent.getData(1); + Task task = list.get(0); + if (task.getKey() == mComic.getId()) { + mBaseView.onTaskAdd(list); + } + } + }); + addSubscription(RxEvent.EVENT_COMIC_UPDATE, new Action1() { + @Override + public void call(RxEvent rxEvent) { + if (mComic.getId() != null && mComic.getId() == (long) rxEvent.getData()) { + Comic comic = mComicManager.load(mComic.getId()); + mComic.setPage(comic.getPage()); + mComic.setLast(comic.getLast()); + mBaseView.onLastChange(mComic.getLast()); + } + } + }); + } + + public Comic getComic() { + return mComic; + } + + private void updateTaskList(List list) { + for (Task task : list) { + int state = task.isFinish() ? Task.STATE_FINISH : Task.STATE_PAUSE; + task.setCid(mComic.getCid()); + task.setSource(mComic.getSource()); + task.setState(state); + } + } + + public void load(long id, final boolean asc) { + mComic = mComicManager.load(id); + mCompositeSubscription.add(mTaskManager.listInRx(id) + .doOnNext(new Action1>() { + @Override + public void call(List list) { + updateTaskList(list); + if (!mComic.getLocal()) { + final List sList = Download.getComicIndex(mBaseView.getAppInstance().getContentResolver(), + mBaseView.getAppInstance().getDocumentFile(), mComic, mSourceManager.getParser(mComic.getSource()).getTitle()); + if (sList != null) { + Collections.sort(list, new Comparator() { + @Override + public int compare(Task lhs, Task rhs) { + return asc ? sList.indexOf(rhs.getPath()) - sList.indexOf(lhs.getPath()) : + sList.indexOf(lhs.getPath()) - sList.indexOf(rhs.getPath()); + } + }); + } + } else { + Collections.sort(list, new Comparator() { + @Override + public int compare(Task lhs, Task rhs) { + return asc ? lhs.getTitle().compareTo(rhs.getTitle()) : + rhs.getTitle().compareTo(lhs.getTitle()); + } + }); + } + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + mBaseView.onTaskLoadSuccess(list, mComic.getLocal()); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onTaskLoadFail(); + } + })); + } + + public void deleteTask(List list, final boolean isEmpty) { + final long id = mComic.getId(); + mCompositeSubscription.add(Observable.just(list) + .subscribeOn(Schedulers.io()) + .doOnNext(new Action1>() { + @Override + public void call(List list) { + deleteFromDatabase(list, isEmpty); + if (!mComic.getLocal()) { + if (isEmpty) { + Download.delete(mBaseView.getAppInstance().getDocumentFile(), mComic, + mSourceManager.getParser(mComic.getSource()).getTitle()); + } else { + Download.delete(mBaseView.getAppInstance().getDocumentFile(), mComic, + list, mSourceManager.getParser(mComic.getSource()).getTitle()); + } + } + } + }) + .compose(new ToAnotherList<>(new Func1() { + @Override + public Long call(Chapter chapter) { + return chapter.getTid(); + } + })) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List list) { + if (isEmpty) { + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_DOWNLOAD_REMOVE, id)); + } + mBaseView.onTaskDeleteSuccess(list); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + mBaseView.onTaskDeleteFail(); + } + })); + } + + private void deleteFromDatabase(final List list, final boolean isEmpty) { + mComicManager.runInTx(new Runnable() { + @Override + public void run() { + for (Chapter chapter : list) { + mTaskManager.delete(chapter.getTid()); + } + if (isEmpty) { + mComic.setDownload(null); + mComicManager.updateOrDelete(mComic); + Download.delete(mBaseView.getAppInstance().getDocumentFile(), mComic, + mSourceManager.getParser(mComic.getSource()).getTitle()); + } + } + }); + } + + public long updateLast(String path) { + if (mComic.getFavorite() != null) { + mComic.setFavorite(System.currentTimeMillis()); + } + mComic.setHistory(System.currentTimeMillis()); + if (!path.equals(mComic.getLast())) { + mComic.setLast(path); + mComic.setPage(1); + } + mComicManager.update(mComic); + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_COMIC_READ, new MiniComic(mComic), false)); + return mComic.getId(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/hiroshi/cimoc/rx/RxBus.java b/app/src/main/java/com/hiroshi/cimoc/rx/RxBus.java new file mode 100644 index 00000000..762d8c67 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/rx/RxBus.java @@ -0,0 +1,48 @@ +package com.hiroshi.cimoc.rx; + +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Func1; +import rx.subjects.PublishSubject; +import rx.subjects.SerializedSubject; +import rx.subjects.Subject; + +/** + * Created by Hiroshi on 2016/8/21. + */ +public class RxBus { + + private static RxBus instance; + + private Subject bus; + + private RxBus() { + bus = new SerializedSubject<>(PublishSubject.create()); + } + + public static RxBus getInstance() { + if (instance == null) { + synchronized (RxBus.class) { + if (instance == null) { + instance = new RxBus(); + } + } + } + return instance; + } + + public void post(RxEvent event) { + bus.onNext(event); + } + + public Observable toObservable(@RxEvent.EventType final int type) { + return bus.ofType(RxEvent.class) + .filter(new Func1() { + @Override + public Boolean call(RxEvent rxEvent) { + return rxEvent.getType() == type; + } + }).onBackpressureBuffer().observeOn(AndroidSchedulers.mainThread()); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/rx/RxEvent.java b/app/src/main/java/com/hiroshi/cimoc/rx/RxEvent.java new file mode 100644 index 00000000..9470255a --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/rx/RxEvent.java @@ -0,0 +1,67 @@ +package com.hiroshi.cimoc.rx; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by Hiroshi on 2016/8/21. + */ +public class RxEvent { + + public static final int EVENT_COMIC_FAVORITE = 1; + public static final int EVENT_COMIC_UNFAVORITE = 2; + public static final int EVENT_COMIC_READ = 3; + public static final int EVENT_COMIC_UPDATE = 4; + public static final int EVENT_COMIC_FAVORITE_RESTORE = 5; + public static final int EVENT_COMIC_HISTORY_RESTORE = 6; + public static final int EVENT_COMIC_CANCEL_HIGHLIGHT = 7; + + public static final int EVENT_TASK_STATE_CHANGE = 21; + public static final int EVENT_TASK_PROCESS = 22; + public static final int EVENT_TASK_INSERT = 23; + + public static final int EVENT_DOWNLOAD_REMOVE = 41; + public static final int EVENT_DOWNLOAD_START = 42; + public static final int EVENT_DOWNLOAD_STOP = 43; + public static final int EVENT_DOWNLOAD_CLEAR = 44; + + public static final int EVENT_TAG_UPDATE = 81; + public static final int EVENT_TAG_RESTORE = 82; + + public static final int EVENT_DIALOG_PROGRESS = 101; + + public static final int EVENT_PICTURE_PAGING = 121; + + public static final int EVENT_SWITCH_NIGHT = 141; + private int type; + private Object[] data; + + public RxEvent(@EventType int type, Object... data) { + this.type = type; + this.data = data; + } + + public @EventType + int getType() { + return type; + } + + public Object getData() { + return getData(0); + } + + public Object getData(int index) { + return index < data.length ? data[index] : null; + } + + @IntDef({EVENT_COMIC_FAVORITE, EVENT_COMIC_UNFAVORITE, EVENT_COMIC_READ, EVENT_COMIC_UPDATE, EVENT_COMIC_FAVORITE_RESTORE, + EVENT_COMIC_HISTORY_RESTORE, EVENT_COMIC_CANCEL_HIGHLIGHT, EVENT_TASK_STATE_CHANGE, EVENT_TASK_PROCESS, + EVENT_TASK_INSERT, EVENT_DOWNLOAD_REMOVE, EVENT_DOWNLOAD_START, EVENT_DOWNLOAD_STOP, EVENT_TAG_UPDATE, + EVENT_TAG_RESTORE, EVENT_DIALOG_PROGRESS, EVENT_DOWNLOAD_CLEAR, EVENT_PICTURE_PAGING, EVENT_SWITCH_NIGHT}) + @Retention(RetentionPolicy.SOURCE) + public @interface EventType { + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/rx/ToAnotherList.java b/app/src/main/java/com/hiroshi/cimoc/rx/ToAnotherList.java new file mode 100644 index 00000000..696a51a2 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/rx/ToAnotherList.java @@ -0,0 +1,30 @@ +package com.hiroshi.cimoc.rx; + +import java.util.List; + +import rx.Observable; +import rx.functions.Func1; + +/** + * Created by Hiroshi on 2016/10/23. + */ + +public class ToAnotherList implements Observable.Transformer, List> { + + private Func1 func; + + public ToAnotherList(Func1 func) { + this.func = func; + } + + @Override + public Observable> call(Observable> observable) { + return observable.flatMap(new Func1, Observable>() { + @Override + public Observable call(List list) { + return Observable.from(list); + } + }).map(func).toList(); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/saf/DocumentFile.java b/app/src/main/java/com/hiroshi/cimoc/saf/DocumentFile.java new file mode 100644 index 00000000..53aa3bd5 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/saf/DocumentFile.java @@ -0,0 +1,105 @@ +package com.hiroshi.cimoc.saf; + +import android.content.Context; +import android.net.Uri; +import android.os.Build; +import android.provider.DocumentsContract; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +/** + * Created by Hiroshi on 2017/3/24. + */ + +public abstract class DocumentFile { + + private final DocumentFile mParent; + + DocumentFile(DocumentFile parent) { + mParent = parent; + } + + public static DocumentFile fromFile(File file) { + return new RawDocumentFile(null, file); + } + + public static DocumentFile fromTreeUri(Context context, Uri treeUri) { + if (Build.VERSION.SDK_INT >= 21) { + Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, + DocumentsContract.getTreeDocumentId(treeUri)); + return new TreeDocumentFile(null, context, documentUri); + } + return null; + } + + public static DocumentFile fromSubTreeUri(Context context, Uri uri) { + if (Build.VERSION.SDK_INT >= 21) { + /* + * https://stackoverflow.com/questions/27759915/bug-when-listing-files-with-android-storage-access-framework-on-lollipop + * 如果使用 buildDocumentUriUsingTree 会获取到授权的那个 DocumentFile + */ + return new TreeDocumentFile(null, context, uri); + } + return null; + } + + public abstract DocumentFile createFile(String displayName); + + public abstract DocumentFile createDirectory(String displayName); + + public abstract Uri getUri(); + + public abstract String getName(); + + public abstract String getType(); + + public DocumentFile getParentFile() { + return mParent; + } + + public abstract boolean isDirectory(); + + public abstract boolean isFile(); + + public abstract long length(); + + public abstract boolean canRead(); + + public abstract boolean canWrite(); + + public abstract boolean delete(); + + public abstract boolean exists(); + + public abstract InputStream openInputStream() throws FileNotFoundException; + + public List listFiles(DocumentFileFilter filter) { + return listFiles(filter, null); + } + + public DocumentFile[] listFiles(Comparator comp) { + DocumentFile[] files = listFiles(); + Arrays.sort(files, comp); + return files; + } + + public abstract List listFiles(DocumentFileFilter filter, Comparator comp); + + public abstract DocumentFile[] listFiles(); + + public abstract void refresh(); + + public abstract DocumentFile findFile(String displayName); + + public abstract boolean renameTo(String displayName); + + public interface DocumentFileFilter { + boolean call(DocumentFile file); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/saf/RawDocumentFile.java b/app/src/main/java/com/hiroshi/cimoc/saf/RawDocumentFile.java new file mode 100644 index 00000000..e7a23163 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/saf/RawDocumentFile.java @@ -0,0 +1,195 @@ +package com.hiroshi.cimoc.saf; + +import android.net.Uri; +import android.webkit.MimeTypeMap; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Created by Hiroshi on 2017/3/24. + */ + +class RawDocumentFile extends DocumentFile { + + private File mFile; + + RawDocumentFile(DocumentFile parent, File file) { + super(parent); + mFile = file; + } + + private static String getTypeForName(String name) { + final int lastDot = name.lastIndexOf('.'); + if (lastDot >= 0) { + final String extension = name.substring(lastDot + 1).toLowerCase(); + final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + if (mime != null) { + return mime; + } + } + + return "application/octet-stream"; + } + + private static boolean deleteContents(File dir) { + File[] files = dir.listFiles(); + boolean success = true; + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + success &= deleteContents(file); + } + if (!file.delete()) { + success = false; + } + } + } + return success; + } + + @Override + public DocumentFile createFile(String displayName) { + File target = new File(mFile, displayName); + if (!target.exists()) { + try { + if (!target.createNewFile()) { + return null; + } + } catch (IOException e) { + return null; + } + } + return new RawDocumentFile(this, target); + } + + @Override + public DocumentFile createDirectory(String displayName) { + final File target = new File(mFile, displayName); + if (target.isDirectory() || target.mkdir()) { + return new RawDocumentFile(this, target); + } + return null; + } + + @Override + public Uri getUri() { + return Uri.fromFile(mFile); + } + + @Override + public String getName() { + return mFile.getName(); + } + + @Override + public String getType() { + if (!mFile.isDirectory()) { + return getTypeForName(mFile.getName()); + } + return null; + } + + @Override + public boolean isDirectory() { + return mFile.isDirectory(); + } + + @Override + public boolean isFile() { + return mFile.isFile(); + } + + @Override + public long length() { + return mFile.length(); + } + + @Override + public boolean canRead() { + return mFile.canRead(); + } + + @Override + public boolean canWrite() { + return mFile.canWrite(); + } + + @Override + public boolean delete() { + deleteContents(mFile); + return mFile.delete(); + } + + @Override + public boolean exists() { + return mFile.exists(); + } + + @Override + public InputStream openInputStream() throws FileNotFoundException { + return new BufferedInputStream(new FileInputStream(mFile)); + } + + @Override + public List listFiles(DocumentFileFilter filter, Comparator comp) { + final ArrayList results = new ArrayList<>(); + final File[] files = mFile.listFiles(); + if (files != null) { + for (File file : files) { + DocumentFile doc = new RawDocumentFile(this, file); + if (filter == null || filter.call(doc)) { + results.add(doc); + } + } + } + if (comp != null) { + Collections.sort(results, comp); + } + return results; + } + + @Override + public DocumentFile[] listFiles() { + final File[] files = mFile.listFiles(); + final DocumentFile[] results = new DocumentFile[files.length]; + for (int i = 0; i < files.length; ++i) { + results[i] = new RawDocumentFile(this, files[i]); + } + return results; + } + + @Override + public void refresh() { + } + + @Override + public DocumentFile findFile(String displayName) { + for (DocumentFile file : listFiles()) { + if (displayName.equals(file.getName())) { + return file; + } + } + return null; + } + + @Override + public boolean renameTo(String displayName) { + final File target = new File(mFile.getParentFile(), displayName); + if (mFile.renameTo(target)) { + mFile = target; + return true; + } else { + return false; + } + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/saf/TreeDocumentFile.java b/app/src/main/java/com/hiroshi/cimoc/saf/TreeDocumentFile.java new file mode 100644 index 00000000..d47d2416 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/saf/TreeDocumentFile.java @@ -0,0 +1,309 @@ +package com.hiroshi.cimoc.saf; + +import android.annotation.TargetApi; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.net.Uri; +import android.provider.DocumentsContract; +import androidx.annotation.RequiresApi; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Created by Hiroshi on 2017/3/24. + */ + +@RequiresApi(21) +@TargetApi(21) +class TreeDocumentFile extends DocumentFile { + + private Context mContext; + private Uri mUri; + private String mDisplayName; + private String mMimeType; + private Map mSubFiles; + + private TreeDocumentFile(DocumentFile parent, Context context, Uri uri, String displayName, String mimeType) { + super(parent); + mContext = context; + mUri = uri; + mDisplayName = displayName; + mMimeType = mimeType; + } + + TreeDocumentFile(DocumentFile parent, Context context, Uri uri) { + super(parent); + mContext = context; + mUri = uri; + query(); + } + + private static void closeQuietly(AutoCloseable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (RuntimeException rethrown) { + throw rethrown; + } catch (Exception ignored) { + } + } + } + + private void list() { + mSubFiles = new HashMap<>(); + + ContentResolver resolver = mContext.getContentResolver(); + Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(mUri, DocumentsContract.getDocumentId(mUri)); + + Cursor c = null; + try { + c = resolver.query(childrenUri, new String[]{DocumentsContract.Document.COLUMN_DOCUMENT_ID, + DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_MIME_TYPE}, null, null, null); + while (c.moveToNext()) { + Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(mUri, c.getString(0)); + String displayName = c.getString(1); + mSubFiles.put(displayName, new TreeDocumentFile(this, mContext, documentUri, displayName, c.getString(2))); + } + } finally { + closeQuietly(c); + } + } + + private void query() { + Cursor c = null; + try { + c = mContext.getContentResolver().query(mUri, new String[]{DocumentsContract.Document.COLUMN_DISPLAY_NAME, + DocumentsContract.Document.COLUMN_MIME_TYPE}, null, null, null); + if (c != null && c.moveToNext()) { + mDisplayName = c.getString(0); + mMimeType = c.getString(1); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + closeQuietly(c); + } + } + + @Override + public DocumentFile createFile(String displayName) { + if (!checkSubFiles()) { + return null; + } + + DocumentFile doc = findFile(displayName); + if (doc != null) { + return null; + } + + try { + Uri result = DocumentsContract.createDocument(mContext.getContentResolver(), mUri, null, displayName); + if (result != null) { + doc = new TreeDocumentFile(this, mContext, result, displayName, null); + mSubFiles.put(displayName, doc); + } + } catch (FileNotFoundException e) { + } + + return doc; + } + + @Override + public DocumentFile createDirectory(String displayName) { + if (!checkSubFiles()) { + return null; + } + + DocumentFile doc = findFile(displayName); + if (doc != null) { + return null; + } + + try { + Uri result = DocumentsContract.createDocument(mContext.getContentResolver(), mUri, + DocumentsContract.Document.MIME_TYPE_DIR, displayName); + if (result != null) { + doc = new TreeDocumentFile(this, mContext, result, displayName, DocumentsContract.Document.MIME_TYPE_DIR); + mSubFiles.put(displayName, doc); + } + } catch (FileNotFoundException e) { + } + + return doc; + } + + @Override + public Uri getUri() { + return mUri; + } + + @Override + public String getName() { + return mDisplayName; + } + + @Override + public String getType() { + return mMimeType; + } + + @Override + public boolean isDirectory() { + return DocumentsContract.Document.MIME_TYPE_DIR.equals(mMimeType); + } + + @Override + public boolean isFile() { + return !isDirectory(); + } + + @Override + public long length() { + Cursor c = null; + try { + c = mContext.getContentResolver().query(mUri, + new String[]{DocumentsContract.Document.COLUMN_SIZE}, null, null, null); + if (c.moveToFirst() && !c.isNull(0)) { + return c.getLong(0); + } else { + return 0; + } + } catch (Exception e) { + e.printStackTrace(); + return 0; + } finally { + closeQuietly(c); + } + } + + @Override + public boolean canRead() { + return mContext.checkCallingOrSelfUriPermission(mUri, Intent.FLAG_GRANT_READ_URI_PERMISSION) + == PackageManager.PERMISSION_GRANTED; + } + + @Override + public boolean canWrite() { + return mContext.checkCallingOrSelfUriPermission(mUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + == PackageManager.PERMISSION_GRANTED; + } + + @Override + public boolean delete() { + try { + if (DocumentsContract.deleteDocument(mContext.getContentResolver(), mUri)) { + // 为求方便,就这样吧 + ((TreeDocumentFile) getParentFile()).mSubFiles.remove(mDisplayName); + return true; + } + } catch (FileNotFoundException e) { + } + return false; + } + + @Override + public boolean exists() { + final ContentResolver resolver = mContext.getContentResolver(); + + Cursor c = null; + try { + c = resolver.query(mUri, new String[]{DocumentsContract.Document.COLUMN_DOCUMENT_ID}, null, null, null); + return c != null && c.getCount() > 0; + } finally { + closeQuietly(c); + } + } + + @Override + public InputStream openInputStream() throws FileNotFoundException { + return mContext.getContentResolver().openInputStream(mUri); + } + + @Override + public List listFiles(DocumentFileFilter filter, Comparator comp) { + if (!checkSubFiles()) { + return new ArrayList<>(); + } + + Iterator> iterator = mSubFiles.entrySet().iterator(); + List list = new ArrayList<>(mSubFiles.size()); + while (iterator.hasNext()) { + DocumentFile file = iterator.next().getValue(); + if (filter == null || filter.call(file)) { + list.add(file); + } + } + + if (comp != null) { + Collections.sort(list, comp); + } + return list; + } + + @Override + public DocumentFile[] listFiles() { + if (!checkSubFiles()) { + return new DocumentFile[0]; + } + + int size = mSubFiles.size(); + Iterator> iterator = mSubFiles.entrySet().iterator(); + DocumentFile[] result = new DocumentFile[size]; + for (int i = 0; i != size; ++i) { + result[i] = iterator.next().getValue(); + } + + return result; + } + + @Override + public void refresh() { + if (mSubFiles != null) { + mSubFiles.clear(); + list(); + } + } + + @Override + public DocumentFile findFile(String displayName) { + if (!checkSubFiles()) { + return null; + } + return mSubFiles.get(displayName); + } + + @Override + public boolean renameTo(String displayName) { + try { + final Uri result = DocumentsContract.renameDocument(mContext.getContentResolver(), mUri, displayName); + if (result != null) { + mUri = result; + return true; + } + } catch (FileNotFoundException e) { + } + return false; + } + + private boolean checkSubFiles() { + if (!isDirectory()) { + return false; + } + if (mSubFiles == null) { + list(); + } + return true; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/service/DownloadService.java b/app/src/main/java/com/hiroshi/cimoc/service/DownloadService.java new file mode 100644 index 00000000..a6149a6f --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/service/DownloadService.java @@ -0,0 +1,294 @@ +package com.hiroshi.cimoc.service; + +import android.app.Service; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import androidx.annotation.Nullable; +import androidx.collection.LongSparseArray; +import android.util.Pair; + +import com.hiroshi.cimoc.App; +import com.hiroshi.cimoc.R; +import com.hiroshi.cimoc.component.AppGetter; +import com.hiroshi.cimoc.core.Download; +import com.hiroshi.cimoc.core.Manga; +import com.hiroshi.cimoc.global.Extra; +import com.hiroshi.cimoc.manager.PreferenceManager; +import com.hiroshi.cimoc.manager.SourceManager; +import com.hiroshi.cimoc.manager.TaskManager; +import com.hiroshi.cimoc.misc.NotificationWrapper; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Task; +import com.hiroshi.cimoc.parser.Parser; +import com.hiroshi.cimoc.rx.RxBus; +import com.hiroshi.cimoc.rx.RxEvent; +import com.hiroshi.cimoc.saf.DocumentFile; +import com.hiroshi.cimoc.utils.DocumentUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import okhttp3.CacheControl; +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** + * Created by Hiroshi on 2016/9/1. + */ +public class DownloadService extends Service implements AppGetter { + + private static final String NOTIFICATION_DOWNLOAD = "NOTIFICATION_DOWNLOAD"; + + private LongSparseArray> mWorkerArray; + private ExecutorService mExecutorService; + private OkHttpClient mHttpClient; + private NotificationWrapper mNotification; + private TaskManager mTaskManager; + private SourceManager mSourceManager; + private ContentResolver mContentResolver; + + public static Intent createIntent(Context context, Task task) { + ArrayList list = new ArrayList<>(1); + list.add(task); + return createIntent(context, list); + } + + public static Intent createIntent(Context context, ArrayList list) { + Intent intent = new Intent(context, DownloadService.class); + intent.putParcelableArrayListExtra(Extra.EXTRA_TASK, list); + return intent; + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return new DownloadServiceBinder(); + } + + @Override + public void onCreate() { + super.onCreate(); + PreferenceManager manager = ((App) getApplication()).getPreferenceManager(); + int num = manager.getInt(PreferenceManager.PREF_DOWNLOAD_THREAD, 2); + mWorkerArray = new LongSparseArray<>(); + mExecutorService = Executors.newFixedThreadPool(num); + mHttpClient = App.getHttpClient(); + mTaskManager = TaskManager.getInstance(this); + mSourceManager = SourceManager.getInstance(this); + mContentResolver = getContentResolver(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent != null) { + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_DOWNLOAD_START)); + if (mNotification == null) { + mNotification = new NotificationWrapper(this, NOTIFICATION_DOWNLOAD, + R.drawable.ic_file_download_white_24dp, true); + mNotification.post(getString(R.string.download_service_doing), true); + } + List list = intent.getParcelableArrayListExtra(Extra.EXTRA_TASK); + for (Task task : list) { + Worker worker = new Worker(task); + Future future = mExecutorService.submit(worker); + addWorker(task.getId(), worker, future); + } + } + return super.onStartCommand(intent, flags, startId); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mNotification != null) { + mExecutorService.shutdownNow(); + notifyCompleted(); + } + } + + @Override + public App getAppInstance() { + return (App) getApplication(); + } + + public synchronized void addWorker(long id, Worker worker, Future future) { + if (mWorkerArray.get(id) == null) { + mWorkerArray.put(id, Pair.create(worker, future)); + } + } + + public synchronized void removeDownload(long id) { + Pair pair = mWorkerArray.get(id); + if (pair != null) { + pair.second.cancel(true); + mWorkerArray.remove(id); + } + } + + public synchronized void completeDownload(long id) { + mWorkerArray.remove(id); + if (mWorkerArray.size() == 0) { + notifyCompleted(); + stopSelf(); + } + } + + private void notifyCompleted() { + if (mNotification != null) { + mNotification.post(getString(R.string.download_service_done), false); + mNotification.cancel(); + mNotification = null; + } + mWorkerArray.clear(); + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_DOWNLOAD_STOP)); + } + + public synchronized void initTask(List list) { + for (Task task : list) { + Pair pair = mWorkerArray.get(task.getId()); + if (pair != null) { + task.setState(pair.first.mTask.getState()); + } + } + } + + public class Worker implements Runnable { + + private Task mTask; + private Parser mParse; + + Worker(Task task) { + mTask = task; + mParse = mSourceManager.getParser(task.getSource()); + } + + @Override + public void run() { + try { + List list = onDownloadParse(); + int size = list.size(); + if (size != 0) { + DocumentFile dir = Download.updateChapterIndex(mContentResolver, getAppInstance().getDocumentFile(), mTask); + if (dir != null) { + mTask.setMax(size); + mTask.setState(Task.STATE_DOING); + boolean success = false; + for (int i = mTask.getProgress(); i < size; ++i) { + onDownloadProgress(i); + ImageUrl image = list.get(i); + int count = 0; // 单页下载错误次数 + success = false; // 是否下载成功 + while (count++ < 20 && !success) { + String[] urls = image.getUrls(); + for (int j = 0; !success && j < urls.length; ++j) { + String url = image.isLazy() ? Manga.getLazyUrl(mParse, urls[j]) : urls[j]; + Request request = buildRequest(mParse.getHeader(url), url); + success = RequestAndWrite(dir, request, i + 1, url); + } + } + if (!success) { // 单页下载错误 + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_TASK_STATE_CHANGE, Task.STATE_ERROR, mTask.getId())); + break; + } + } + if (success) { + onDownloadProgress(size); + } + } else { + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_TASK_STATE_CHANGE, Task.STATE_ERROR, mTask.getId())); + } + } else { + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_TASK_STATE_CHANGE, Task.STATE_ERROR, mTask.getId())); + } + } catch (InterruptedIOException e) { + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_TASK_STATE_CHANGE, Task.STATE_PAUSE, mTask.getId())); + } + + completeDownload(mTask.getId()); + } + + private boolean RequestAndWrite(DocumentFile parent, Request request, int num, String url) throws InterruptedIOException { + if (request != null) { + Response response = null; + try { + response = mHttpClient.newCall(request).execute(); + if (response.isSuccessful()) { + String displayName = buildFileName(num, url); + DocumentFile file = DocumentUtils.getOrCreateFile(parent, displayName); + DocumentUtils.writeBinaryToFile(mContentResolver, file, response.body().byteStream()); + return true; + } + } catch (SocketTimeoutException e) { + e.printStackTrace(); + } catch (InterruptedIOException e) { + // 由暂停下载引发,需要抛出以便退出外层循环,结束任务 + throw e; + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (response != null) { + response.close(); + } + } + } + return false; + } + + private Request buildRequest(Headers headers, String url) { + if (StringUtils.isEmpty(url)) { + return null; + } + + return new Request.Builder() + .cacheControl(new CacheControl.Builder().noStore().build()) + .headers(headers) + .url(url) + .get() + .build(); + } + + private String buildFileName(int num, String url) { + String suffix = StringUtils.split(url, "\\.", -1); + if (suffix == null) { + suffix = "jpg"; + } else { + suffix = suffix.split("\\?")[0]; + } + return StringUtils.format("%03d.%s", num, suffix); + } + + private List onDownloadParse() throws InterruptedIOException { + mTask.setState(Task.STATE_PARSE); + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_TASK_STATE_CHANGE, Task.STATE_PARSE, mTask.getId())); + return Manga.getImageUrls(mParse, mTask.getSource(), mTask.getCid(), mTask.getPath()); + } + + private void onDownloadProgress(int progress) { + mTask.setProgress(progress); + mTaskManager.update(mTask); + RxBus.getInstance().post(new RxEvent(RxEvent.EVENT_TASK_PROCESS, mTask.getId(), progress, mTask.getMax())); + } + + } + + public class DownloadServiceBinder extends Binder { + + public DownloadService getService() { + return DownloadService.this; + } + + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/soup/MDocument.java b/app/src/main/java/com/hiroshi/cimoc/soup/MDocument.java new file mode 100755 index 00000000..2b333d0b --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/soup/MDocument.java @@ -0,0 +1,29 @@ +package com.hiroshi.cimoc.soup; + + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.select.Elements; + +/** + * Created by reborn on 18-1-21. + */ + +public class MDocument { + + private Document document; + + public MDocument(String html) { + this.document = Jsoup.parse(html); + } + + public String text(String cssQuery) { + try { +// Elements elements = document.getElementsByTag("script").eq(7); + Elements elements = document.select(cssQuery); + return String.valueOf(elements.first()); + } catch (Exception e) { + return null; + } + } +} diff --git a/app/src/main/java/com/hiroshi/cimoc/soup/Node.java b/app/src/main/java/com/hiroshi/cimoc/soup/Node.java new file mode 100755 index 00000000..c81cb330 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/soup/Node.java @@ -0,0 +1,174 @@ +package com.hiroshi.cimoc.soup; + +import com.hiroshi.cimoc.utils.StringUtils; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.LinkedList; +import java.util.List; + +/** + * Created by Hiroshi on 2016/9/11. + */ +public class Node { + + private Element element; + + public Node(String html) { + this.element = Jsoup.parse(html).body(); + } + + public Node(Element element) { + this.element = element; + } + + public Node id(String id) { + return new Node(element.getElementById(id)); + } + + public Node getChild(String cssQuery) { + return new Node(get().select(cssQuery).first()); + } + + public Node getLastChild(String cssQuery) { + return new Node(get().select(cssQuery).last()); + } + + public List list(String cssQuery) { + List list = new LinkedList<>(); + Elements elements = element.select(cssQuery); + for (Element e : elements) { + list.add(new Node(e)); + } + return list; + } + + public Element get() { + return element; + } + + public String text() { + try { + return element.text().trim(); + } catch (Exception e) { + return null; + } + } + + public String text(String cssQuery) { + try { + return element.select(cssQuery).first().text().trim(); + } catch (Exception e) { + return null; + } + } + + public String textWithSubstring(String cssQuery, int start, int end) { + return StringUtils.substring(text(cssQuery), start, end); + } + + public String textWithSubstring(String cssQuery, int start) { + return textWithSubstring(cssQuery, start, -1); + } + + public String textWithSplit(String cssQuery, String regex, int index) { + return StringUtils.split(text(cssQuery), regex, index); + } + + public String attr(String attr) { + try { + return element.attr(attr).trim(); + } catch (Exception e) { + return null; + } + } + + public String attr(String cssQuery, String attr) { + try { + return element.select(cssQuery).first().attr(attr).trim(); + } catch (Exception e) { + return null; + } + } + + public String attrWithSubString(String attr, int start, int end) { + return StringUtils.substring(attr(attr), start, end); + } + + public String attrWithSubString(String attr, int start) { + return attrWithSubString(attr, start, -1); + } + + public String attrWithSubString(String cssQuery, String attr, int start, int end) { + return StringUtils.substring(attr(cssQuery, attr), start, end); + } + + public String attrWithSubString(String cssQuery, String attr, int start) { + return attrWithSubString(cssQuery, attr, start, -1); + } + + public String attrWithSplit(String attr, String regex, int index) { + return StringUtils.split(attr(attr), regex, index); + } + + public String attrWithSplit(String cssQuery, String attr, String regex, int index) { + return StringUtils.split(attr(cssQuery, attr), regex, index); + } + + public String src() { + return attr("src"); + } + + public String src(String cssQuery) { + return attr(cssQuery, "src"); + } + + public String dataUrl(String cssQuery) { + return attr(cssQuery, "data-url"); + } + + public String href() { + return attr("href"); + } + + public String href(String cssQuery) { + return attr(cssQuery, "href"); + } + + public String hrefWithSubString(int start, int end) { + return attrWithSubString("href", start, end); + } + + public String hrefWithSubString(int start) { + return hrefWithSubString(start, -1); + } + + public String hrefWithSubString(String cssQuery, int start, int end) { + return attrWithSubString(cssQuery, "href", start, end); + } + + public String hrefWithSubString(String cssQuery, int start) { + return hrefWithSubString(cssQuery, start, -1); + } + + public String hrefWithSplit(int index) { + return splitHref(href(), index); + } + + public String hrefWithSplit(String cssQuery, int index) { + return splitHref(href(cssQuery), index); + } + + static public String splitHref(String str, int index) { + if (str == null) { + return null; + } + str = str.replaceFirst(".*\\..*?/", ""); + str = str.replaceAll("[/\\.=\\?]", " "); + str = str.trim(); + return StringUtils.split(str, "\\s+", index); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/Animx2.java b/app/src/main/java/com/hiroshi/cimoc/source/Animx2.java new file mode 100644 index 00000000..fa3e9d40 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/Animx2.java @@ -0,0 +1,159 @@ +package com.hiroshi.cimoc.source; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by FEILONG on 2017/12/21. + */ + +public class Animx2 extends MangaParser { + + public static final int TYPE = 55; + public static final String DEFAULT_TITLE = "2animx"; + + public Animx2(Source source) { + init(source, null); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException { + if (page != 1) return null; + String url = StringUtils.format("http://www.2animx.com/search-index?searchType=1&q=%s&page=%d", keyword, page); + return new Request.Builder().url(url).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("ul.liemh > li")) { + @Override + protected Comic parse(Node node) { + String cid = node.hrefWithSplit("a", 0); + String title = node.text("a > div.tit"); + String cover = "http://www.2animx.com" + node.attr("a > img", "src"); + String update = node.text("a > font"); + return new Comic(TYPE, cid, title, cover, update, ""); + } + }; + } + + @Override + public String getUrl(String cid) { + return cid; + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("m.bnmanhua.com", ".*", 0)); + } + + @Override + public Request getInfoRequest(String cid) { + if (cid.indexOf("http://www.2animx.com") == -1) { + cid = "http://www.2animx.com/".concat(cid); + } + return new Request.Builder().url(cid).addHeader("Cookie", "isAdult=1").build(); + } + + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + String title = body.text("div.position > strong"); + String cover = "http://www.2animx.com/" + body.src("dl.mh-detail > dt > a > img"); + String update = ""; + String author = ""; + String intro = body.text(".mh-introduce"); + boolean status = false; + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + for (Node node : new Node(html).list("div#oneCon2 > ul > li")) { + String title = node.attr("a", "title"); + Matcher mTitle = Pattern.compile("\\d+").matcher(title); + title = mTitle.find() ? mTitle.group() : title; + String path2 = node.href("a"); + String path = node.hrefWithSplit("a", 0); + list.add(new Chapter(title, path)); + } + return list; + } + + private String _cid, _path; + + @Override + public Request getImagesRequest(String cid, String path) { + if (path.indexOf("http://www.2animx.com") == -1) { + path = "http://www.2animx.com/".concat(path); + } + _cid = cid; + _path = path; + return new Request.Builder().url(path).addHeader("Cookie", "isAdult=1").build(); + } + + @Override + public List parseImages(String html) { + List list = new ArrayList<>(); + Matcher pageMatcher = Pattern.compile("id=\"total\" value=\"(.*?)\"").matcher(html); + if (!pageMatcher.find()) return null; + int page = Integer.parseInt(pageMatcher.group(1)); + for (int i = 1; i <= page; ++i) { + list.add(new ImageUrl(i, StringUtils.format("%s-p-%d", _path, i), true)); + } + return list; + } + + @Override + public Request getLazyRequest(String url) { + return new Request.Builder() +// .addHeader("Referer", url) + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 7.0;) Chrome/58.0.3029.110 Mobile") + .addHeader("Cookie", "isAdult=1") + .url(url).build(); + } + + @Override + public String parseLazy(String html, String url) { + Matcher m = Pattern.compile("<\\/div>).matcher(html);
+        if (m.find()) {
+            return m.group(1);
+        }
+        return null;
+    }
+
+    @Override
+    public Request getCheckRequest(String cid) {
+        return getInfoRequest(cid);
+    }
+
+    @Override
+    public Headers getHeader(String url) {
+        return Headers.of( li.vbox")) { + @Override + protected Comic parse(Node node) { + String title = node.attr("a.vbox_t", "title"); + String cid = node.attr("a.vbox_t", "href").substring(7); + String cover = node.attr("a.vbox_t > mip-img", "src"); + String update = node.text("h4:eq(2)"); // 从1开始 + return new Comic(TYPE, cid, title, cover, update, null); + } + }; + } + + @Override + public String getUrl(String cid) { + return "http://m.bnmanhua.com/comic/".concat(cid); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("m.bnmanhua.com")); + } + + @Override + public Request getInfoRequest(String cid) { + String url = cid.indexOf(".html") > 0 ? "http://m.bnmanhua.com/comic/".concat(cid) + : "http://m.bnmanhua.com/comic/".concat(cid).concat(".html"); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + String cover = body.attr("div.dbox > div.img > mip-img", "src"); + String title = body.text("div.dbox > div.data > h4"); + String intro = body.text("div.tbox_js"); + String author = body.text("div.dbox > div.data > p.dir").substring(3).trim(); + String update = body.text("div.dbox > div.data > p.act").substring(3, 13).trim(); + boolean status = isFinish(body.text("span.list_item")); + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + // 此处得到的章节列表是从 第1话 开始的 + for (Node node : new Node(html).list("div.tabs_block > ul > li > a")) { + String title = node.text(); + String path = node.hrefWithSplit(2); + // 将新得到的章节插入到链表的开头 + list.add(0, new Chapter(title, path)); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + cid = cid.substring(0, cid.length() - 5); + String url = StringUtils.format("http://m.bnmanhua.com/comic/%s/%s.html", cid, path); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + String url = StringUtils.match("z_yurl='(.*?)'", html, 1); + String str = StringUtils.match("z_img=\'\\[(.*?)\\]\'", html, 1); + if (str != null && !str.equals("")) { + try { + String[] array = str.split(","); + for (int i = 0; i != array.length; ++i) { + String[] ss = array[i].split("\\\\/"); + String lastStr = null; + String prevStr = null; + String s = null; + if (ss.length > 5) { + prevStr = ss[3] + "/" + ss[4] + "/"; + lastStr = ss[7].substring(0, ss[7].length() - 1); + s = ss[5] + "/" + ss[6] + "/" + lastStr; + } else { + lastStr = ss[4].substring(0, ss[4].length() - 1); // 需要去掉末尾的双引号 + prevStr = ss[0].substring(1) + "/" + ss[1] + "/"; // 需要去掉开头的双引号 + s = ss[2] + "/" + ss[3] + "/" + lastStr; + } + +// http://bnpic.comic123.net/upload/files/15/2619/15489140020.jpg +// http://bnpic.comic123.net/images/comic/2ain: github.com. Please check that this domain has been added to a service.5/48730/1522165706dcYCM4Z4HjkbKrlQ.jpg + list.add(new ImageUrl(i + 1, url + prevStr + s, false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + // 这里表示的是 parseInfo 的更新时间 + return new Node(html).text("div.dbox > div.data > p.act").substring(3, 13).trim(); + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "https://m.bnmanhua.com"); + } +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/BuKa.java b/app/src/main/java/com/hiroshi/cimoc/source/BuKa.java new file mode 100644 index 00000000..54d28c9b --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/BuKa.java @@ -0,0 +1,189 @@ +package com.hiroshi.cimoc.source; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.JsonIterator; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.Request; +import okhttp3.RequestBody; + +/** + * Created by FEILONG on 2017/12/21. + */ + +public class BuKa extends MangaParser { + + public static final int TYPE = 52; + public static final String DEFAULT_TITLE = "布卡漫画"; + + public BuKa(Source source) { + init(source, null); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException { + String url = "http://m.buka.cn/search/ajax_search"; + RequestBody data = new FormBody.Builder() + .add("key", keyword) + .add("start", String.valueOf(15 * (page - 1))) + .add("count", "15") + .build();//key=%E4%B8%8D%E5%AE%9C%E5%AB%81&start=0&count=15 + return new Request.Builder() + .url(url) + .post(data) + .build(); + } + + @Override + public SearchIterator getSearchIterator(String json, int page) { + try { + JSONObject object = new JSONObject(json); + JSONObject dataObject = object.getJSONObject("datas"); + JSONArray dataArray = dataObject.getJSONArray("items"); + return new JsonIterator(dataArray) { + @Override + protected Comic parse(JSONObject object) { + try { + String cid = object.getString("mid"); + String title = object.getString("name"); + String cover = object.getString("logo"); + String author = object.getString("author"); + return new Comic(TYPE, cid, title, cover, null, author); + } catch (Exception ex) { + return null; + } + } + }; + } catch (Exception ex) { + return null; + } + } + + @Override + public String getUrl(String cid) { + return "http://m.buka.cn/m/".concat(cid); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("m.buka.cn")); + } + + @Override + public Request getInfoRequest(String cid) { + String url = "http://m.buka.cn/m/".concat(cid); + return new Request.Builder() + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 7.0;) Chrome/58.0.3029.110 Mobile") + .url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + String title = body.text("p.mangadir-glass-name"); + String cover = body.src(".mangadir-glass-img > img"); + String update = body.text("span.top-title-right"); + String author = body.text(".mangadir-glass-author"); + String intro = body.text("span.description_intro"); + boolean status = isFinish("连载中");//todo: fix here + comic.setInfo(title, cover, update, intro, author, status); + } + +// @Override +// public Request getChapterRequest(String html, String cid){ +// String url = "https://m.ac.qq.com/comic/chapterList/id/".concat(cid); +// return new Request.Builder() +// .url(url) +// .build(); +// } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + for (Node node : new Node(html).list("div.chapter-center > a")) { + String title = node.text(); + String path = node.href().split("/")[3]; + list.add(new Chapter(title, path)); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("http://m.buka.cn/read/%s/%s", cid, path); + return new Request.Builder() + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 7.0;) Chrome/58.0.3029.110 Mobile") + .url(url) + .build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + Matcher m = Pattern.compile("").matcher(html); + if (m.find()) { + try { + int i = 0; + do { + list.add(new ImageUrl(++i, StringUtils.match("http.*jpg", m.group(0), 0), false)); + } while (m.find()); + } catch (Exception e) { + e.printStackTrace(); + } + } + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).text("div.book-detail > div.cont-list > dl:eq(2) > dd"); + } + + @Override + public List parseCategory(String html, int page) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list("li > a")) { + String cid = node.hrefWithSplit(1); + String title = node.text("h3"); + String cover = node.attr("div > img", "data-src"); + String update = node.text("dl:eq(5) > dd"); + String author = node.text("dl:eq(2) > dd"); + list.add(new Comic(TYPE, cid, title, cover, update, author)); + } + return list; + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://m.buka.cn"); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/CCMH.java b/app/src/main/java/com/hiroshi/cimoc/source/CCMH.java new file mode 100644 index 00000000..c3ad7f89 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/CCMH.java @@ -0,0 +1,190 @@ +package com.hiroshi.cimoc.source; + +import com.google.common.collect.Lists; +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.Request; +import okhttp3.RequestBody; + +/** + * Created by WinterWhisper on 2019/2/25. + */ +public class CCMH extends MangaParser { + + public static final int TYPE = 23; + public static final String DEFAULT_TITLE = "CC漫画"; + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + public CCMH(Source source) { + init(source, null); + } + + @Override + public Request getSearchRequest(String keyword, int page) { + String url = ""; + if (page == 1) { + url = "http://m.ccmh6.com/Search"; + + RequestBody requestBodyPost = new FormBody.Builder() + .add("Key", keyword) + .build(); + + return new Request.Builder() + .addHeader("Referer", "http://m.ccmh6.com/Search") + .addHeader("Origin", "http://m.ccmh6.com") + .addHeader("Host", "m.ccmh6.com") + .addHeader("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/12.0 Mobile/15A372 Safari/604.1") + .url(url) + .post(requestBodyPost) + .build(); + + } + return null; + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list(".list > div")) { + @Override + protected Comic parse(Node node) { + String cid = node.hrefWithSplit("a", 1); + String title = node.textWithSplit("a", "\\s+", 0); + String cover = node.src("a > img"); +// if (cover.startsWith("//")) cover = "https:" + cover; +// String update = node.text(".itemTxt > p.txtItme:eq(3)"); +// boolean finish = node.textWithSplit("a","\\s+",1) == "完结"; + String author = node.textWithSplit("a", "\\s+", 2); + return new Comic(TYPE, cid, title, cover, "", author); + } + }; + } + + @Override + public String getUrl(String cid) { + return StringUtils.format("http://m.ccmh6.com/manhua/%s", cid); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("m.50mh.com", "manhua\\/(\\w+)", 1)); + } + + + @Override + public Request getInfoRequest(String cid) { + String url = StringUtils.format("http://m.ccmh6.com/manhua/%s", cid); + return new Request.Builder() + .addHeader("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/12.0 Mobile/15A372 Safari/604.1") + .url(url) + .build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String intro = body.text(".intro"); + String title = body.text(".other > div > strong"); + String cover = body.src(".cover > img"); +// if (cover.startsWith("//")) cover = "https:" + cover; + String author = body.textWithSplit(".other", "\\s+|:", 8); + String update = body.textWithSplit(".other", "\\s+|:", 12) + .replace("[", "").replace("]", ""); + boolean status = isFinish(body.textWithSplit(".other", "\\s+|:", 10)); + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list(".list > a")) { + String title = node.attr("title"); + String path = node.hrefWithSplit(2); + list.add(new Chapter(title, path)); + } + + return Lists.reverse(list); + } + + private String _cid, _path; + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("http://m.ccmh6.com/manhua/%s/%s.html", cid, path); + _cid = cid; + _path = path; + return new Request.Builder() + .addHeader("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/12.0 Mobile/15A372 Safari/604.1") + .url(url) + .build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + + //find image count + Matcher pageCountMatcher = Pattern.compile("\\d+<\\/a>").matcher(html); + int pageCount = 0; + while (pageCountMatcher.find()) { + final int pageCountTemp = Integer.parseInt(pageCountMatcher.group(1)); + pageCount = pageCount > pageCountTemp ? pageCount : pageCountTemp; + } + + for (int i = 0; i < pageCount; i++) { + list.add(new ImageUrl(i, StringUtils.format("http://m.ccmh6.com/manhua/%s/%s.html?p=%d", _cid, _path, i + 1), true)); + } + return list; + } + + @Override + public Request getLazyRequest(String url) { + return new Request.Builder() + .addHeader("Referer", StringUtils.format("http://m.ccmh6.com/manhua/%s/%s.html", _cid, _path)) + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 7.0;) Chrome/58.0.3029.110 Mobile") + .url(url).build(); + } + + @Override + public String parseLazy(String html, String url) { + Node body = new Node(html); + String src = body.src(".img > img"); + return src; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).text(".Introduct_Sub > .sub_r > .txtItme:eq(4)"); + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://m.ccmh6.com/"); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/CCTuku.java b/app/src/main/java/com/hiroshi/cimoc/source/CCTuku.java new file mode 100644 index 00000000..5d06c4ad --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/CCTuku.java @@ -0,0 +1,264 @@ +package com.hiroshi.cimoc.source; + +import android.util.Pair; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaCategory; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by Hiroshi on 2016/7/28. + * neew fix + */ +public class CCTuku extends MangaParser { + + public static final int TYPE = 3; + public static final String DEFAULT_TITLE = "CC图库"; + + public CCTuku(Source source) { + init(source, new Category()); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, false); + } + + @Override + public Request getSearchRequest(String keyword, int page) { + if(page == 1) { + String url = StringUtils.format("http://m.tuku.cc/search-%s/?language=1", keyword); + return new Request.Builder().url(url).build(); + } else return null; + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("ul.searchResultList > li")) { + @Override + protected Comic parse(Node node) { + String cid = node.hrefWithSplit("a", 1); + String title = node.text("p.title"); + String cover = node.src("a > img"); + return new Comic(TYPE, cid, title, cover, null, null); + } + }; + } + + @Override + public String getUrl(String cid) { + return "http://m.tuku.cc/comic/".concat(cid); + } + + @Override + public Request getInfoRequest(String cid) { + String url = "http://m.tuku.cc/comic/".concat(cid); + return new Request.Builder() + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 7.0;) Chrome/58.0.3029.110 Mobile") + .url(url) + .build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String title = body.text("div.detailTop > div.content > div.info > p.title"); + String cover = body.src("div.detailTop > div.content > img"); + String update = body.textWithSubstring("#chapter > div > div > div.top > span", 0, 10); + // FIXME 这里可能有多个作者 暂时先取第一个 + String author = body.text("div.detailTop > div.content > div.info > p:eq(1) > a"); + String intro = body.text("div.detailContent > p:eq(1)"); + // FIXME 手机版页面好像获取不到状态 电脑板页面太大不想用 暂时先固定为连载吧 + comic.setInfo(title, cover, update, intro, author, false); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list("#chapter > div > div > ul > li > a")) { + String title = node.text(); +// String path = StringUtils.split(node.href(), "/", 3); + String path = node.hrefWithSplit(2); + list.add(new Chapter(title, path)); + } + return list; + } + + private String _cid, _path; + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("http://m.tuku.cc/comic/%s/%s/", cid, path); + _cid = cid; + _path = path; + return new Request.Builder() + .addHeader("Referer", StringUtils.format("http://m.tuku.cc/comic/%s/", cid)) + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 7.0;) Chrome/58.0.3029.110 Mobile") + .url(url).build(); + } + + @Override + public List parseImages(String html) { + // TODO 好像拿不到总页数 GG + List list = new ArrayList<>(); +// Node body = new Node(html); +// int page = Integer.parseInt(body.attr("#hdPageCount", "value"));//max pages unknow... + int page = 10; + for (int i = 1; i <= page; ++i) { + list.add(new ImageUrl(i, StringUtils.format("http://m.tuku.cc/comic/%s/%s/p%s/", _cid, _path, i), true)); + } + return list; + } + + public Request getLazyRequest(String url) { + return new Request.Builder() + .addHeader("Referer", StringUtils.format("http://m.tuku.cc/comic/%s/%s/", _cid, _path)) + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 7.0;) Chrome/58.0.3029.110 Mobile") + .url(url).build(); + } + + @Override + public String parseLazy(String html, String url) { + Node body = new Node(html); + String src = body.href("div.readForm > a"); + return src; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).textWithSubstring("div.book > div > div.row > div:eq(1) > div > dl:eq(5) > dd > font", 0, 10); + } + + @Override + public List parseCategory(String html, int page) { + List list = new LinkedList<>(); + Node body = new Node(html); + int total = Integer.parseInt(StringUtils.match("\\d+", body.text("div.title-banner > div > h1"), 0)); + if (page <= total) { + for (Node node : body.list("div.main-list > div > div > div")) { + String cid = node.hrefWithSplit("div:eq(1) > div:eq(0) > a", 1); + String title = node.text("div:eq(1) > div:eq(0) > a"); + String cover = node.src("div:eq(0) > a > img"); + String update = node.text("div:eq(1) > div:eq(1) > dl:eq(3) > dd > font"); + String author = node.text("div:eq(1) > div:eq(1) > dl:eq(1) > dd > a"); + list.add(new Comic(TYPE, cid, title, cover, update, author)); + } + } + return list; + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://m.tuku.cc"); + } + + private static class Category extends MangaCategory { + + @Override + public String getFormat(String... args) { + if (!"".equals(args[CATEGORY_SUBJECT])) { + return StringUtils.format("http://m.tuku.cc/list/list_%s_%%d.htm", args[CATEGORY_SUBJECT]); + } else if (!"".equals(args[CATEGORY_AREA])) { + return StringUtils.format("http://m.tuku.cc/list/comic_%s_%%d.htm", args[CATEGORY_AREA]); + } else if (!"".equals(args[CATEGORY_PROGRESS])) { + return StringUtils.format("http://m.tuku.cc/%s/%%d", args[CATEGORY_PROGRESS]); + } else { + return "http://m.tuku.cc/newest/%d"; + } + } + + @Override + protected List> getSubject() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("魔幻", "1")); + list.add(Pair.create("动作", "2")); + list.add(Pair.create("热血", "5")); + list.add(Pair.create("爱情", "4")); + list.add(Pair.create("武侠", "15")); + list.add(Pair.create("搞笑", "7")); + list.add(Pair.create("校园", "20")); + list.add(Pair.create("竞技", "3")); + list.add(Pair.create("科幻", "11")); + list.add(Pair.create("悬疑", "10")); + list.add(Pair.create("拳皇", "12")); + list.add(Pair.create("恐怖", "9")); + list.add(Pair.create("美女", "19")); + list.add(Pair.create("励志", "8")); + list.add(Pair.create("历史", "22")); + list.add(Pair.create("百合", "35")); + list.add(Pair.create("猎奇", "39")); + list.add(Pair.create("职场", "38")); + list.add(Pair.create("短篇", "34")); + list.add(Pair.create("美食", "31")); + list.add(Pair.create("四格", "30")); + list.add(Pair.create("同人", "18")); + list.add(Pair.create("青年", "17")); + list.add(Pair.create("游戏", "14")); + list.add(Pair.create("街霸", "13")); + list.add(Pair.create("萌系", "6")); + list.add(Pair.create("机战", "43")); + list.add(Pair.create("节操", "42")); + list.add(Pair.create("伪娘", "41")); + list.add(Pair.create("后宫", "40")); + list.add(Pair.create("耽美", "16")); + list.add(Pair.create("其它", "33")); + list.add(Pair.create("轻小说", "21")); + return list; + } + + @Override + protected boolean hasArea() { + return true; + } + + @Override + protected List> getArea() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("香港", "1")); + list.add(Pair.create("日本", "2")); + list.add(Pair.create("欧美", "5")); + list.add(Pair.create("台湾", "4")); + list.add(Pair.create("韩国", "15")); + list.add(Pair.create("大陆", "7")); + return list; + } + + @Override + protected boolean hasProgress() { + return true; + } + + @Override + protected List> getProgress() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("连载", "lianzai")); + list.add(Pair.create("完结", "wanjie")); + return list; + } + + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/Cartoonmad.java b/app/src/main/java/com/hiroshi/cimoc/source/Cartoonmad.java new file mode 100644 index 00000000..40f308c5 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/Cartoonmad.java @@ -0,0 +1,165 @@ +package com.hiroshi.cimoc.source; + +import com.google.common.collect.Lists; +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.RegexIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.Request; +import okhttp3.RequestBody; + +/** + * Created by FEILONG on 2017/12/21. + */ + +public class Cartoonmad extends MangaParser { + + public static final int TYPE = 54; + public static final String DEFAULT_TITLE = "动漫狂"; + + public Cartoonmad(Source source) { + init(source, null); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException { + if (page != 1) return null; + String url = "https://www.cartoonmad.com/search.html"; + RequestBody body = new FormBody.Builder() + .add("keyword", URLEncoder.encode(keyword, "BIG5")) + .add("searchtype", "all") + .build(); + return new Request.Builder().url(url).post(body).addHeader("Referer", "https://www.cartoonmad.com/").build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Pattern pattern = Pattern.compile("<\\/span> dd"); +// String author = node.text("dl:eq(2) > dd"); + return new Comic(TYPE, cid, title, cover, "", ""); + } + }; + } + + @Override + public String getUrl(String cid) { + return "https://www.cartoonmad.com/comic/".concat(cid).concat(".html"); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("www.cartoonmad.com")); + } + + @Override + public Request getInfoRequest(String cid) { + String url = "https://www.cartoonmad.com/comic/".concat(cid).concat(".html"); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + Matcher mTitle = Pattern.compile("<\\/div>").matcher(html); + String intro = mInro.find() ? mInro.group(1) : ""; + boolean status = false; + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + Matcher mChapter = Pattern.compile("(.*?)<\\/a> ").matcher(html); + while (mChapter.find()) { + String title = mChapter.group(2); + String path = mChapter.group(1); + list.add(new Chapter(title, path)); + } + return Lists.reverse(list); + } + + private String _cid, _path; + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("https://www.cartoonmad.com%s", path); + _cid = cid; + _path = path; + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new ArrayList<>(); + Matcher pageMatcher = Pattern.compile(".*(.*?)<\\/a>").matcher(html); + if (!pageMatcher.find()) return null; + int page = Integer.parseInt(pageMatcher.group(2)); + for (int i = 1; i <= page; ++i) { + list.add(new ImageUrl(i, StringUtils.format("https://www.cartoonmad.com/comic/%s%03d.html", pageMatcher.group(1), i), true)); + } + return list; + } + + @Override + public Request getLazyRequest(String url) { + return new Request.Builder() + .addHeader("Referer", url) + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 7.0;) Chrome/58.0.3029.110 Mobile") + .url(url).build(); + } + + @Override + public String parseLazy(String html, String url) { + Matcher m = Pattern.compile(" ul > li")) { + @Override + protected Comic parse(Node node) { + Node node_cover = node.list("p > a").get(0); + String cid = node_cover.hrefWithSplit(1); + String title = node_cover.attr("img", "alt"); + String cover = node_cover.attr("img", "_src"); + String update = node.text("dl > dd > p:eq(0) > span"); + return new Comic(TYPE, cid, title, cover, update, null); + } + }; + } + + @Override + public Request getInfoRequest(String cid) { + String url = "http://www.chuixue.net/manhua/".concat(cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + String title = body.text("div.intro_l > div.title > h1"); + String cover = body.src("div.intro_l > div.info_cover > p.cover > img"); + String update = body.text("div.intro_l > div.info > p:eq(0) > span").substring(0, 10); + String author = body.text("div.intro_l > div.info > p:eq(1)").substring(5).trim(); + String intro = body.text("#intro"); + boolean status = isFinish(body.text("div.intro_l > div.info > p:eq(2)")); + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + for (Node node : new Node(html).list("#play_0 > ul > li > a")) { + String title = node.attr("title"); + String path = node.hrefWithSplit(2); + list.add(new Chapter(title, path)); + } + return list; + } + + private String _cid = ""; + private String _path = ""; + + @Override + public Request getImagesRequest(String cid, String path) { + _cid = cid; + _path = path; + + String url = StringUtils.format("http://www.chuixue.net/manhua/%s/%s.html", cid, path); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + String str = StringUtils.match("photosr\\[1\\](.*?)(\n|var)", html, 1); + if (str != null) { + try { + String[] array = str.split(";"); + for (int i = 0; i != array.length; ++i) { + String s_full = array[i].trim(); + int index = s_full.indexOf("="); + String s = s_full.substring(index + 2, s_full.length() - 1); + list.add(new ImageUrl(i + 1, "http://chuixue1.tianshigege.com/" + s, false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + // 这里表示的是更新时间 + return new Node(html).text("div.intro_l > div.info > p:eq(0) > span").substring(0, 10); + } + + @Override + public Headers getHeader() { + String referer = StringUtils.format("http://www.chuixue.net/manhua/%s/%s.html", _cid, _path); + + return Headers.of("Referer", referer); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/Chuiyao.java b/app/src/main/java/com/hiroshi/cimoc/source/Chuiyao.java new file mode 100644 index 00000000..2be49389 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/Chuiyao.java @@ -0,0 +1,197 @@ +package com.hiroshi.cimoc.source; + +import android.util.Pair; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaCategory; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.json.JSONArray; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by Hiroshi on 2016/11/28. + */ + +public class Chuiyao extends MangaParser { + + public static final int TYPE = 9; + public static final String DEFAULT_TITLE = "吹妖漫画"; + + public Chuiyao(Source source) { + init(source, new Category()); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) { + String url = StringUtils.format("http://m.chuiyao.com/search/?page=%d&ajax=1&key=%s&act=search&order=1", page, keyword); + return new Request.Builder().url(url).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("li > a")) { + @Override + protected Comic parse(Node node) { + String cid = node.hrefWithSplit(1); + String title = node.text("h3"); + String cover = node.attr("div > img", "data-src"); + String update = node.text("dl:eq(5) > dd"); + String author = node.text("dl:eq(2) > dd"); + return new Comic(TYPE, cid, title, cover, update, author); + } + }; + } + + @Override + public String getUrl(String cid) { + return "http://m.chuiyao.com/manhua/".concat(cid); + } + + @Override + public Request getInfoRequest(String cid) { + String url = "http://m.chuiyao.com/manhua/".concat(cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String title = body.text("div.main-bar > h1"); + String cover = body.src("div.book-detail > div.cont-list > div.thumb > img"); + String update = body.text("div.book-detail > div.cont-list > dl:eq(2) > dd"); + String author = body.text("div.book-detail > div.cont-list > dl:eq(3) > dd"); + String intro = body.text("#bookIntro"); + boolean status = isFinish(body.text("div.book-detail > div.cont-list > div.thumb > i")); + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + for (Node node : new Node(html).list("#chapterList > ul > li > a")) { + String title = node.attr("title"); + String path = node.hrefWithSplit(2); + list.add(new Chapter(title, path)); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("http://m.chuiyao.com/manhua/%s/%s.html", cid, path); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + String str = StringUtils.match("parseJSON\\('(.*?)'\\)", html, 1); + if (str != null) { + try { + JSONArray array = new JSONArray(str); + for (int i = 0; i != array.length(); ++i) { + list.add(new ImageUrl(i + 1, array.getString(i), false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).text("div.book-detail > div.cont-list > dl:eq(2) > dd"); + } + + @Override + public List parseCategory(String html, int page) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list("li > a")) { + String cid = node.hrefWithSplit(1); + String title = node.text("h3"); + String cover = node.attr("div > img", "data-src"); + String update = node.text("dl:eq(5) > dd"); + String author = node.text("dl:eq(2) > dd"); + list.add(new Comic(TYPE, cid, title, cover, update, author)); + } + return list; + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://m.chuiyao.com"); + } + + private static class Category extends MangaCategory { + + @Override + public boolean isComposite() { + return true; + } + + @Override + public String getFormat(String... args) { + return StringUtils.format("http://m.chuiyao.com/act/?act=list&page=%%d&catid=%s&ajax=1&order=%s", + args[CATEGORY_SUBJECT], args[CATEGORY_ORDER]); + } + + @Override + protected List> getSubject() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("最近更新", "0")); + list.add(Pair.create("少年热血", "1")); + list.add(Pair.create("武侠格斗", "2")); + list.add(Pair.create("科幻魔幻", "3")); + list.add(Pair.create("竞技体育", "4")); + list.add(Pair.create("爆笑喜剧", "5")); + list.add(Pair.create("侦探推理", "6")); + list.add(Pair.create("恐怖灵异", "7")); + list.add(Pair.create("少女爱情", "8")); + list.add(Pair.create("恋爱生活", "9")); + return list; + } + + @Override + protected boolean hasOrder() { + return true; + } + + @Override + protected List> getOrder() { + List> list = new ArrayList<>(); + list.add(Pair.create("更新", "3")); + list.add(Pair.create("发布", "1")); + list.add(Pair.create("人气", "2")); + return list; + } + + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/DM5.java b/app/src/main/java/com/hiroshi/cimoc/source/DM5.java new file mode 100644 index 00000000..aabd5f77 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/DM5.java @@ -0,0 +1,328 @@ +package com.hiroshi.cimoc.source; + +import android.util.Pair; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.JsonIterator; +import com.hiroshi.cimoc.parser.MangaCategory; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.DecryptionUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.Request; +import okhttp3.RequestBody; + +/** + * Created by Hiroshi on 2016/8/25. + */ +public class DM5 extends MangaParser { + + public static final int TYPE = 5; + public static final String DEFAULT_TITLE = "动漫屋"; + + public DM5(Source source) { + init(source, new Category()); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) { + String url = "http://m.dm5.com/pagerdata.ashx"; + RequestBody body = new FormBody.Builder() + .add("t", "7") + .add("pageindex", String.valueOf(page)) + .add("title", keyword) + .build(); + return new Request.Builder().url(url).post(body).addHeader("Referer", "http://m.dm5.com").build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + try { + return new JsonIterator(new JSONArray(html)) { + @Override + protected Comic parse(JSONObject object) { + try { + String cid = object.getString("Url").split("/")[1]; + String title = object.getString("Title"); + String cover = object.getString("Pic"); + String update = object.getString("LastPartTime"); + JSONArray array = object.optJSONArray("Author"); + String author = ""; + for (int i = 0; array != null && i != array.length(); ++i) { + author = author.concat(array.optString(i)); + } + return new Comic(TYPE, cid, title, cover, update, author); + } catch (JSONException e) { + e.printStackTrace(); + } + return null; + } + }; + } catch (JSONException e) { + return null; + } + } + + @Override + public String getUrl(String cid) { + return "http://www.dm5.com/".concat(cid); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("www.dm5.com", "/([\\w\\-]+)")); + filter.add(new UrlFilter("tel.dm5.com", "/([\\w\\-]+)")); + } + + @Override + public Request getInfoRequest(String cid) { + String url = "http://www.dm5.com/".concat(cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String title = body.textWithSplit("div.banner_detail_form > div.info > p.title", " ", 0); + String cover = body.src("div.banner_detail_form > div.cover > img"); + String update = body.text("#tempc > div.detail-list-title > span.s > span"); + if (update != null) { + Calendar calendar = Calendar.getInstance(); + if (update.contains("今天") || update.contains("分钟前")) { + update = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.getTime()); + } else if (update.contains("昨天")) { + calendar.add(Calendar.DATE, -1); + update = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.getTime()); + } else if (update.contains("前天")) { + calendar.add(Calendar.DATE, -2); + update = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.getTime()); + } else { + String result = StringUtils.match("\\d+-\\d+-\\d+", update, 0); + if (result == null) { + String[] rs = StringUtils.match("(\\d+)月(\\d+)号", update, 1, 2); + if (rs != null) { + result = calendar.get(Calendar.YEAR) + "-" + rs[0] + "-" + rs[1]; + } + } + update = result; + } + } + String author = body.text("div.banner_detail_form > div.info > p.subtitle > a"); + String intro = body.text("div.banner_detail_form > div.info > p.content"); + if (intro != null) { + intro = intro.replace("[+展开]", "").replace("[-折叠]", ""); + } + boolean status = isFinish(body.text("div.banner_detail_form > div.info > p.tip > span:eq(0)")); + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + Set set = new LinkedHashSet<>(); + Node body = new Node(html); + for (Node node : body.list("#chapterlistload > ul li > a")) { + String title = StringUtils.split(node.text(), " ", 0); + String path = node.hrefWithSplit(0); + set.add(new Chapter(title, path)); + } + return new LinkedList<>(set); + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = "http://m.dm5.com/".concat(path); + return new Request + .Builder() + .addHeader("Referer", StringUtils.format("http://m.dm5.com/%s", path)) + .url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + String str = StringUtils.match("eval\\(.*\\)", html, 0); + if (str != null) { + try { + str = DecryptionUtils.evalDecrypt(str, "newImgs"); + String[] array = str.split(","); + for (int i = 0; i != array.length; ++i) { + list.add(new ImageUrl(i + 1, array[i], false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return list; + } + + @Override + public Request getLazyRequest(String url) { + return new Request.Builder().url(url) + .addHeader("Referer", "http://www.dm5.com") + .build(); + } + + @Override + public String parseLazy(String html, String url) { + String result = DecryptionUtils.evalDecrypt(html); + if (result != null) { + return result.split(",")[0]; + } + return null; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).textWithSubstring("#mhinfo > div.innr9 > div.innr90 > div.innr92 > span:eq(9)", 5, -10); + } + + @Override + public List parseCategory(String html, int page) { + List list = new ArrayList<>(); + Node body = new Node(html); + for (Node node : body.list("ul.mh-list > li > div.mh-item")) { + String cid = node.hrefWithSplit("div > h2.title > a", 0); + String title = node.text("div > h2.title > a"); + String cover = StringUtils.match("\\((.*?)\\)", node.attr("p.mh-cover", "style"), 1); + String author = node.textWithSubstring("p.author", 3); + // String update = node.text("p.zl"); 要解析好麻烦 + list.add(new Comic(TYPE, cid, title, cover, null, author)); + } + return list; + } + + @Override + public Headers getHeader(String url) { + String cid = "m".concat(StringUtils.match("cid=(\\d+)", url, 1)); + return Headers.of("Referer", "http://m.dm5.com/".concat(cid)); + } + + @Override + public Headers getHeader(List list) { + String cid = ""; + if (list != null) { + cid = list.get(0).getChapter(); + } + return Headers.of("Referer", "http://m.dm5.com/".concat(cid)); + } + + private static class Category extends MangaCategory { + + @Override + public boolean isComposite() { + return true; + } + + @Override + public String getFormat(String... args) { + String path = args[CATEGORY_SUBJECT].concat(" ").concat(args[CATEGORY_AREA]).concat(" ").concat(args[CATEGORY_PROGRESS]) + .concat(" ").concat(args[CATEGORY_ORDER]).trim(); + path = path.replaceAll("\\s+", "-"); + return StringUtils.format("http://www.dm5.com/manhua-list-%s-p%%d", path); + } + + @Override + protected List> getSubject() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("热血", "tag31")); + list.add(Pair.create("恋爱", "tag26")); + list.add(Pair.create("校园", "tag1")); + list.add(Pair.create("百合", "tag3")); + list.add(Pair.create("耽美", "tag27")); + list.add(Pair.create("冒险", "tag2")); + list.add(Pair.create("后宫", "tag8")); + list.add(Pair.create("科幻", "tag25")); + list.add(Pair.create("战争", "tag12")); + list.add(Pair.create("悬疑", "tag17")); + list.add(Pair.create("推理", "tag33")); + list.add(Pair.create("搞笑", "tag37")); + list.add(Pair.create("奇幻", "tag14")); + list.add(Pair.create("魔法", "tag15")); + list.add(Pair.create("恐怖", "tag29")); + list.add(Pair.create("神鬼", "tag20")); + list.add(Pair.create("历史", "tag4")); + list.add(Pair.create("同人", "tag30")); + list.add(Pair.create("运动", "tag34")); + list.add(Pair.create("绅士", "tag36")); + list.add(Pair.create("机战", "tag40")); + return list; + } + + @Override + protected boolean hasArea() { + return true; + } + + @Override + protected List> getArea() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("港台", "area35")); + list.add(Pair.create("日韩", "area36")); + list.add(Pair.create("内地", "area37")); + list.add(Pair.create("欧美", "area38")); + return list; + } + + @Override + public boolean hasProgress() { + return true; + } + + @Override + public List> getProgress() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("连载", "st1")); + list.add(Pair.create("完结", "st2")); + return list; + } + + @Override + protected boolean hasOrder() { + return true; + } + + @Override + protected List> getOrder() { + List> list = new ArrayList<>(); + list.add(Pair.create("更新", "s2")); + list.add(Pair.create("人气", "")); + list.add(Pair.create("新品上架", "s18")); + return list; + } + + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/Dmzj.java b/app/src/main/java/com/hiroshi/cimoc/source/Dmzj.java new file mode 100644 index 00000000..2a82a5c7 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/Dmzj.java @@ -0,0 +1,314 @@ +package com.hiroshi.cimoc.source; + +import android.util.Pair; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.JsonIterator; +import com.hiroshi.cimoc.parser.MangaCategory; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by Hiroshi on 2016/7/8. + */ +public class Dmzj extends MangaParser { + + public static final int TYPE = 1; + public static final String DEFAULT_TITLE = "动漫之家"; + + public Dmzj(Source source) { + init(source, new Category()); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) { + if (page == 1) { + String url = StringUtils.format("http://s.acg.dmzj.com/comicsum/search.php?s=%s", keyword, page - 1); + return new Request.Builder().url(url).build(); + } + return null; + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + try { + Pattern p = Pattern.compile("(\\[.*?\\])"); + Matcher m = p.matcher(html); + if (!m.find()) + return null; + + return new JsonIterator(new JSONArray(m.group(1))) { + @Override + protected Comic parse(JSONObject object) { + try { + String cid = object.getString("id"); + String title = object.getString("name"); + String cover = object.getString("comic_cover"); + String author = object.optString("authors"); + return new Comic(TYPE, cid, title, cover, null, author); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + }; + } catch (JSONException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public String getUrl(String cid) { + return StringUtils.format("http://m.dmzj.com/info/%s.html", cid); + } + + @Override + public Request getInfoRequest(String cid) { + String url = StringUtils.format("http://v2.api.dmzj.com/comic/%s.json", cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + try { + JSONObject object = new JSONObject(html); + String title = object.getString("title"); + String cover = object.getString("cover"); + Long time = object.has("last_updatetime") ? object.getLong("last_updatetime") * 1000 : null; + String update = time == null ? null : StringUtils.getFormatTime("yyyy-MM-dd", time); + String intro = object.optString("description"); + StringBuilder sb = new StringBuilder(); + JSONArray array = object.getJSONArray("authors"); + for (int i = 0; i < array.length(); ++i) { + sb.append(array.getJSONObject(i).getString("tag_name")).append(" "); + } + String author = sb.toString(); + boolean status = object.getJSONArray("status").getJSONObject(0).getInt("tag_id") == 2310; + comic.setInfo(title, cover, update, intro, author, status); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + try { + JSONObject object = new JSONObject(html); + JSONArray array = object.getJSONArray("chapters"); + for (int i = 0; i != array.length(); ++i) { + JSONArray data = array.getJSONObject(i).getJSONArray("data"); + for (int j = 0; j != data.length(); ++j) { + JSONObject chapter = data.getJSONObject(j); + String title = chapter.getString("chapter_title"); + String path = chapter.getString("chapter_id"); + list.add(new Chapter(title, path)); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("http://v2.api.dmzj.com/chapter/%s/%s.json", cid, path); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + try { + JSONObject object = new JSONObject(html); + JSONArray array = object.getJSONArray("page_url"); + for (int i = 0; i < array.length(); ++i) { + list.add(new ImageUrl(i + 1, array.getString(i), false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + try { + JSONObject object = new JSONObject(html); + long time = object.getLong("last_updatetime") * 1000; + return new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date(time)); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + @Override + public List parseCategory(String html, int page) { + List list = new LinkedList<>(); + try { + JSONArray array = new JSONArray(html); + for (int i = 0; i != array.length(); ++i) { + try { + JSONObject object = array.getJSONObject(i); + if (object.optInt("hidden", 1) != 1) { + String cid = object.getString("id"); + String title = object.getString("name"); + String cover = "http://images.dmzj.com/".concat(object.getString("cover")); + Long time = object.has("last_updatetime") ? object.getLong("last_updatetime") * 1000 : null; + String update = time == null ? null : new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date(time)); + String author = object.optString("authors"); + list.add(new Comic(TYPE, cid, title, cover, update, author)); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + return list; + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://m.dmzj.com/"); + } + + private static class Category extends MangaCategory { + + @Override + public boolean isComposite() { + return true; + } + + @Override + public String getFormat(String... args) { + return StringUtils.format("http://m.dmzj.com/classify/%s-%s-%s-%s-%s-%%d.json", + args[CATEGORY_SUBJECT], args[CATEGORY_READER], args[CATEGORY_PROGRESS], args[CATEGORY_AREA], args[CATEGORY_ORDER]); + } + + @Override + public List> getSubject() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "0")); + list.add(Pair.create("冒险", "1")); + list.add(Pair.create("欢乐向", "2")); + list.add(Pair.create("格斗", "3")); + list.add(Pair.create("科幻", "4")); + list.add(Pair.create("爱情", "5")); + list.add(Pair.create("竞技", "6")); + list.add(Pair.create("魔法", "7")); + list.add(Pair.create("校园", "8")); + list.add(Pair.create("悬疑", "9")); + list.add(Pair.create("恐怖", "10")); + list.add(Pair.create("生活亲情", "11")); + list.add(Pair.create("百合", "12")); + list.add(Pair.create("伪娘", "13")); + list.add(Pair.create("耽美", "14")); + list.add(Pair.create("后宫", "15")); + list.add(Pair.create("萌系", "16")); + list.add(Pair.create("治愈", "17")); + list.add(Pair.create("武侠", "18")); + list.add(Pair.create("职场", "19")); + list.add(Pair.create("奇幻", "20")); + list.add(Pair.create("节操", "21")); + list.add(Pair.create("轻小说", "22")); + list.add(Pair.create("搞笑", "23")); + return list; + } + + @Override + public boolean hasArea() { + return true; + } + + @Override + public List> getArea() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "0")); + list.add(Pair.create("日本", "1")); + list.add(Pair.create("内地", "2")); + list.add(Pair.create("欧美", "3")); + list.add(Pair.create("港台", "4")); + list.add(Pair.create("韩国", "5")); + list.add(Pair.create("其他", "6")); + return list; + } + + @Override + public boolean hasReader() { + return true; + } + + @Override + public List> getReader() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "0")); + list.add(Pair.create("少年", "1")); + list.add(Pair.create("少女", "2")); + list.add(Pair.create("青年", "3")); + return list; + } + + @Override + public boolean hasProgress() { + return true; + } + + @Override + public List> getProgress() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "0")); + list.add(Pair.create("连载", "1")); + list.add(Pair.create("完结", "2")); + return list; + } + + @Override + public boolean hasOrder() { + return true; + } + + @Override + public List> getOrder() { + List> list = new ArrayList<>(); + list.add(Pair.create("更新", "1")); + list.add(Pair.create("人气", "0")); + return list; + } + + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/Dmzjv2.java b/app/src/main/java/com/hiroshi/cimoc/source/Dmzjv2.java new file mode 100644 index 00000000..e21a071f --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/Dmzjv2.java @@ -0,0 +1,328 @@ +package com.hiroshi.cimoc.source; + +import android.util.Pair; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.JsonIterator; +import com.hiroshi.cimoc.parser.MangaCategory; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by Hiroshi on 2016/7/8. + */ +public class Dmzjv2 extends MangaParser { + + public static final int TYPE = 10; + public static final String DEFAULT_TITLE = "动漫之家v2"; + +// private List filter = new ArrayList<>(); + + public Dmzjv2(Source source) { + init(source, new Category()); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("manhua.dmzj.com", "/(\\w+)")); + filter.add(new UrlFilter("m.dmzj.com", "/info/(\\w+).html")); + } + + @Override + public Request getSearchRequest(String keyword, int page) { + if (page == 1) { + String url = StringUtils.format("https://m.dmzj.com/search/%s.html", keyword); + return new Request.Builder().url(url).build(); + } + return null; + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + try { + //var serchArry=[{"id":27445,"name":"\u5982\u679c\u6709\u59b9\u59b9\u5c31\u597d\u4e86@comic","comic_py":"ruguoyoumeimeijiuhaole","alias_name":"\u8981\u662f\u6709\u4e2a\u59b9\u59b9\u5c31\u597d\u4e86","authors":"\u5e73\u5742\u8bfb\/\u3044\uff5e\u3069\u3045\uff5e","types":"\u7231\u60c5\/\u540e\u5bab\/\u8f7b\u5c0f\u8bf4","zone":"\u65e5\u672c","status":"\u8fde\u8f7d\u4e2d","last_update_chapter_name":"\u7b2c24\u8bdd","last_update_chapter_id":75090,"hot_hits":16106154,"last_updatetime":1521125443,"description":"\u75af\u72c2\u7684\u5c0f\u8bf4\u5bb6\u7fbd\u5c9b\u4f0a\u6708\uff0c\u662f\u65e5\u591c\u4e3a\u4e86\u521b\u9020\u51fa\u672a\u66fe\u8c0b\u9762\u7684\u59b9\u59b9\u5f62\u8c61\u800c\u594b\u6597\u7684\u73b0\u4ee3\u76ae\u683c\u9a6c\u5229\u7fc1\u3002\u4ed6\u7684\u8eab\u8fb9\u805a\u96c6\u4e86\u4f5c\u5bb6\u3001\u753b\u5e08\u3001\u7f16\u8f91\u3001\u7a0e\u52a1\u4eba\u7b49\u5145\u6ee1\u4e2a\u6027\u7684\u602a\u4eba\uff1a\u7231\u4e0e\u624d\u80fd\u90fd\u662f\u91cd\u91cf\u7ea7\u7684\u6781\u81f4\u6b8b\u5ff5\u7cfb\u7f8e\u5c11\u5973\u53ef\u513f\u90a3\u7531\u591a\uff0c\u70e6\u607c\u7740\u604b\u7231\u70e6\u607c\u7740\u53cb\u60c5\u70e6\u607c\u7740\u68a6\u60f3\u7684\u9752\u6625\u4e09\u51a0\u738b\u767d\u5ddd\u4eac\uff0c\u80f8\u6000\u5927\u5fd7\u7684\u5e05\u54e5\u738b\u5b50\u4e0d\u7834\u6625\u6597\uff0c\u8f7b\u89c6\u4eba\u751f\u7684\u5929\u624d\u63d2\u753b\u5e08\u60e0\u90a3\u5239\u90a3\uff0c\u867d\u7136\u5f88\u53ef\u9760\u4f46\u662f\u5374\u4e0d\u60f3\u4f9d\u9760\u4ed6\u7684\u9b3c\u755c\u7a0e\u6b3e\u5b88\u62a4\u4eba\u5927\u91ce\u963f\u4ec0\u5229\uff0c\u5185\u5fc3\u9634\u6697\u7684\u7f16\u8f91\u571f\u5c90\u5065\u6b21\u90ce...","cover":"webpic\/19\/ruguoyoumeimei03029.jpg"},{"id":41016,"name":"\u5982\u679c\u6709\u59b9\u59b9\u5c31\u597d\u4e86\u5916\u4f20","comic_py":"ruguoyoumeimeijiuhaolewaizhuan","alias_name":"\u8981\u662f\u6709\u4e2a\u59b9\u59b9\u5c31\u597d\u4e86,\u53ea\u8981\u6210\u4e3a\u59b9\u59b9\u5c31\u597d\u4e86","authors":"\u5e73\u5742\u8bfb\/\u30b3\u30d0\u30b7\u30b3","types":"\u7231\u60c5\/\u540e\u5bab\/\u8f7b\u5c0f\u8bf4","zone":"\u65e5\u672c","status":"\u8fde\u8f7d\u4e2d","last_update_chapter_name":"\u7b2c02\u8bdd","last_update_chapter_id":66979,"hot_hits":1864533,"last_updatetime":1503305259,"description":"\u8981\u662f\u80fd\u88ab\u5f53\u6210\u59b9\u59b9\u5c31\u597d\u4e86\uff01\u6210\u4e3a\u59b9\u59b9\u5927\u4f5c\u6218\u3002","cover":"webpic\/5\/ysygmmjhlwz6486l.jpg"},{"id":8031,"name":"\u66fe\u7ecf_\u5982\u679c\u6709\u5e86\u795d\u65e5\u7684\u8bdd","comic_py":"zjrgyqzrdh","alias_name":"","authors":"\u30b9\u30bf\u30b8\u30aa\u30cb\u30ca\u30ca","types":"\u751f\u6d3b\/\u6821\u56ed","zone":"\u65e5\u672c","status":"\u8fde\u8f7d\u4e2d","last_update_chapter_name":"\u5168\u4e00\u8bdd","last_update_chapter_id":15129,"hot_hits":4175,"last_updatetime":1323068912,"description":"\u5982\u679c\u4f60\u65e9\u8d77\u53d1\u73b0\u65c1\u8fb9\u8eba\u7740\u4e00\u4e2a\u4eba\uff0c\u90a3\u4eba\u5c45\u7136\u8bf4\u5c31\u662f\u672a\u6765\u7684\u4f60\uff01\u59d0\u5e73\u9759\u7684\u751f\u6d3b\u5c31\u6b64\u65e0\u6cd5\u6de1\u5b9a.","cover":"webpic\/18\/ruguoyoude.jpg"},{"id":19239,"name":"\u5149\u955c\u00b7poker","comic_py":"guangjingpoker","alias_name":"","authors":"\u5982\u679c\u6709\u5982\u679cwuyu","types":"\u5192\u9669","zone":"\u5185\u5730","status":"\u8fde\u8f7d\u4e2d","last_update_chapter_name":"\u7b2c01\u8bdd \u660e\u4e89\u6697\u6597","last_update_chapter_id":38205,"hot_hits":274,"last_updatetime":1434533642,"description":"\u5149\u955c\u00b7poker","cover":"img\/webpic\/12\/1002082321434533480.jpg"}] + String str = StringUtils.match("serchArry=(\\[.*?\\])", html, 1); + return new JsonIterator(new JSONArray(str)) { + @Override + protected Comic parse(JSONObject object) { + try { + String cid = object.getString("id"); + String title = object.getString("name"); + String cover = "https://images.dmzj.com/".concat(object.getString("cover")); + String author = object.optString("authors"); + return new Comic(TYPE, cid, title, cover, null, author); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + }; + } catch (JSONException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public String getUrl(String cid) { + return StringUtils.format("http://m.dmzj.com/info/%s.html", cid); + } + + @Override + public Request getInfoRequest(String cid) { + String url = StringUtils.format("http://m.dmzj.com/info/%s.html", cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String intro = body.textWithSubstring("p.txtDesc", 3); + String title = body.attr("#Cover > img", "title"); + String cover = body.src("#Cover > img"); + String author = body.text("div.Introduct_Sub > div.sub_r > p:eq(0) > a"); + String update = body.textWithSubstring("div.Introduct_Sub > div.sub_r > p:eq(3) > span.date", 0, 10); + boolean status = isFinish(body.text("div.Introduct_Sub > div.sub_r > p:eq(2) > a:eq(3)")); + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + String jsonString = StringUtils.match("initIntroData\\((.*?)\\);", html, 1); + List list = new LinkedList<>(); + if (jsonString != null) { + try { + JSONArray array = new JSONArray(jsonString); + for (int i = 0; i != array.length(); ++i) { + JSONArray data = array.getJSONObject(i).getJSONArray("data"); + for (int j = 0; j != data.length(); ++j) { + JSONObject object = data.getJSONObject(j); + String title = object.getString("chapter_name"); + String path = object.getString("id"); + list.add(new Chapter(title, path)); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("http://m.dmzj.com/view/%s/%s.html", cid, path); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + String jsonString = StringUtils.match("\"page_url\":(\\[.*?\\]),", html, 1); + if (jsonString != null) { + try { + JSONArray array = new JSONArray(jsonString); + for (int i = 0; i != array.length(); ++i) { + list.add(new ImageUrl(i + 1, array.getString(i), false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).textWithSubstring("div.Introduct_Sub > div.sub_r > p:eq(3) > span.date", 0, 10); + } + + @Override + public List parseCategory(String html, int page) { + List list = new LinkedList<>(); + try { + JSONArray array = new JSONArray(html); + for (int i = 0; i != array.length(); ++i) { + try { + JSONObject object = array.getJSONObject(i); + String cid = object.getString("id"); + String title = object.getString("title"); + String cover = object.getString("cover"); + Long time = object.has("last_updatetime") ? object.getLong("last_updatetime") * 1000 : null; + String update = time == null ? null : new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date(time)); + String author = object.optString("authors"); + list.add(new Comic(TYPE, cid, title, cover, update, author)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + return list; + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://images.dmzj.com/"); + } + + private static class Category extends MangaCategory { + + @Override + public boolean isComposite() { + return true; + } + + @Override + public String getFormat(String... args) { + String path = args[CATEGORY_SUBJECT].concat(" ").concat(args[CATEGORY_READER]).concat(" ").concat(args[CATEGORY_PROGRESS]) + .concat(" ").concat(args[CATEGORY_AREA]).trim(); + if (path.isEmpty()) { + path = String.valueOf(0); + } else { + path = path.replaceAll("\\s+", "-"); + } + return StringUtils.format("http://v2.api.dmzj.com/classify/%s/%s/%%d.json", path, args[CATEGORY_ORDER]); + } + + @Override + public List> getSubject() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("冒险", "4")); + list.add(Pair.create("百合", "3243")); + list.add(Pair.create("生活", "3242")); + list.add(Pair.create("四格", "17")); + list.add(Pair.create("伪娘", "3244")); + list.add(Pair.create("悬疑", "3245")); + list.add(Pair.create("后宫", "3249")); + list.add(Pair.create("热血", "3248")); + list.add(Pair.create("耽美", "3246")); + list.add(Pair.create("其他", "16")); + list.add(Pair.create("恐怖", "14")); + list.add(Pair.create("科幻", "7")); + list.add(Pair.create("格斗", "6")); + list.add(Pair.create("欢乐向", "5")); + list.add(Pair.create("爱情", "8")); + list.add(Pair.create("侦探", "9")); + list.add(Pair.create("校园", "13")); + list.add(Pair.create("神鬼", "12")); + list.add(Pair.create("魔法", "11")); + list.add(Pair.create("竞技", "10")); + list.add(Pair.create("历史", "3250")); + list.add(Pair.create("战争", "3251")); + list.add(Pair.create("魔幻", "5806")); + list.add(Pair.create("扶她", "5345")); + list.add(Pair.create("东方", "5077")); + list.add(Pair.create("奇幻", "5848")); + list.add(Pair.create("轻小说", "6316")); + list.add(Pair.create("仙侠", "7900")); + list.add(Pair.create("搞笑", "7568")); + list.add(Pair.create("颜艺", "6437")); + list.add(Pair.create("性转换", "4518")); + list.add(Pair.create("高清单行", "4459")); + list.add(Pair.create("治愈", "3254")); + list.add(Pair.create("宅系", "3253")); + list.add(Pair.create("萌系", "3252")); + list.add(Pair.create("励志", "3255")); + list.add(Pair.create("节操", "6219")); + list.add(Pair.create("职场", "3328")); + list.add(Pair.create("西方魔幻", "3365")); + list.add(Pair.create("音乐舞蹈", "3326")); + list.add(Pair.create("机战", "3325")); + return list; + } + + @Override + public boolean hasArea() { + return true; + } + + @Override + public List> getArea() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("日本", "2304")); + list.add(Pair.create("韩国", "2305")); + list.add(Pair.create("欧美", "2306")); + list.add(Pair.create("港台", "2307")); + list.add(Pair.create("内地", "2308")); + list.add(Pair.create("其他", "8453")); + return list; + } + + @Override + public boolean hasReader() { + return true; + } + + @Override + public List> getReader() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("少年", "3262")); + list.add(Pair.create("少女", "3263")); + list.add(Pair.create("青年", "3264")); + return list; + } + + @Override + public boolean hasProgress() { + return true; + } + + @Override + public List> getProgress() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("连载", "2309")); + list.add(Pair.create("完结", "2310")); + return list; + } + + @Override + public boolean hasOrder() { + return true; + } + + @Override + public List> getOrder() { + List> list = new ArrayList<>(); + list.add(Pair.create("更新", "1")); + list.add(Pair.create("人气", "0")); + return list; + } + + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/EHentai.java b/app/src/main/java/com/hiroshi/cimoc/source/EHentai.java new file mode 100644 index 00000000..1d691f2d --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/EHentai.java @@ -0,0 +1,146 @@ +package com.hiroshi.cimoc.source; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.util.LinkedList; +import java.util.List; + +import okhttp3.Request; + +/** + * Created by Hiroshi on 2016/7/26. + */ +public class EHentai extends MangaParser { + + public static final int TYPE = 60; + public static final String DEFAULT_TITLE = "EHentai"; + + public EHentai(Source source) { + init(source, null); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) { + String url = ""; + if (page == 1) + url = StringUtils.format("https://e-hentai.org/?f_doujinshi=1&f_manga=1&f_artistcg=1&f_gamecg=1&f_western=1&f_non-h=1&f_imageset=1&f_cosplay=1&f_asianporn=1&f_misc=1&f_search=%s&f_apply=Apply+Filter", keyword); + return new Request.Builder().url(url).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("div.ido > div > table.itg > tbody > tr[class^=gtr]")) { + @Override + protected Comic parse(Node node) { + String cid = node.hrefWithSubString("td:eq(2) > div > div:eq(2) > a", 23, -2); + String title = node.text("td:eq(2) > div > div:eq(2) > a"); + String cover = node.src("td:eq(2) > div > div:eq(0) > img"); + if (cover == null) { + String temp = node.textWithSubstring("td:eq(2) > div > div:eq(0)", 14).split("~", 2)[0]; + cover = "http://ehgt.org/".concat(temp); + } + String update = node.textWithSubstring("td:eq(1)", 0, 10); + String author = StringUtils.match("\\[(.*?)\\]", title, 1); + title = title.replaceFirst("\\[.*?\\]\\s*", ""); + return new Comic(TYPE, cid, title, cover, update, author); + } + }; + } + + @Override + public Request getInfoRequest(String cid) { + String url = StringUtils.format("http://g.e-hentai.org/g/%s", cid); + return new Request.Builder().url(url).header("Cookie", "nw=1").build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String update = body.textWithSubstring("#gdd > table > tbody > tr:eq(0) > td:eq(1)", 0, 10); + String title = body.text("#gn"); + String intro = body.text("#gj"); + String author = body.text("#taglist > table > tbody > tr > td:eq(1) > div > a[id^=ta_artist]"); +// String cover = body.href("#gdt > .gdtm > div > a"); + String cover = "https://github.com/feilongfl/Cimoc/raw/release-tci/screenshot/icon.png"; + comic.setInfo(title, cover, update, intro, author, true); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + Node body = new Node(html); + String length = body.textWithSplit("#gdd > table > tbody > tr:eq(5) > td:eq(1)", " ", 0); + int size = Integer.parseInt(length) % 40 == 0 ? Integer.parseInt(length) / 40 : Integer.parseInt(length) / 40 + 1; + for (int i = 0; i != size; ++i) { + list.add(0, new Chapter("Ch" + i, String.valueOf(i))); + } + return list; + } + +// @Override +// public Request getRecentRequest(int page) { +// String url = StringUtils.format("http://g.e-hentai.org/?page=%d", (page - 1)); +// return new Request.Builder().url(url).build(); +// } + +// @Override +// public List parseRecent(String html, int page) { +// List list = new LinkedList<>(); +// Node body = new Node(html); +// for (Node node : body.list("table.itg > tbody > tr[class^=gtr]")) { +// String cid = node.hrefWithSubString("td:eq(2) > div > div:eq(2) > a", 24, -2); +// String title = node.text("td:eq(2) > div > div:eq(2) > a"); +// String cover = node.attr("td:eq(2) > div > div:eq(0) > img", "src"); +// if (cover == null) { +// String temp = node.textWithSubstring("td:eq(2) > div > div:eq(0)", 14).split("~", 2)[0]; +// cover = "http://ehgt.org/".concat(temp); +// } +// String update = node.textWithSubstring("td:eq(1)", 0, 10); +// String author = StringUtils.match("\\[(.*?)\\]", title, 1); +// title = title.replaceFirst("\\[.*?\\]\\s*", ""); +// list.add(new Comic(TYPE, cid, title, cover, update, author)); +// } +// return list; +// } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("http://g.e-hentai.org/g/%s/?p=%s", cid, path); + return new Request.Builder().url(url).header("Cookie", "nw=1").build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + Node body = new Node(html); + int count = 0; + for (Node node : body.list("#gdt > div > div > a")) { + list.add(new ImageUrl(++count, node.href(), true)); + } + return list; + } + + @Override + public Request getLazyRequest(String url) { + return new Request.Builder().url(url).build(); + } + + @Override + public String parseLazy(String html, String url) { + return new Node(html).src("a > img[style]"); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/ExHentai.java b/app/src/main/java/com/hiroshi/cimoc/source/ExHentai.java new file mode 100644 index 00000000..7683a35c --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/ExHentai.java @@ -0,0 +1,117 @@ +package com.hiroshi.cimoc.source; + +/** + * Created by Hiroshi on 2016/8/6. + */ +public class ExHentai/* extends MangaParser */ { +/* + @Override + public Request getSearchRequest(String keyword, int page) { + String url = StringUtils.format("https://exhentai.org?f_search=%s&page=%d", keyword, (page - 1)); + return new Request.Builder().url(url).header("Cookie", "ipb_member_id=2145630; ipb_pass_hash=f883b5a9dd10234c9323957b96efbd8e").build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("table.itg > tbody > tr[class^=gtr]")) { + @Override + protected Comic parse(Node node) { + String cid = node.hrefWithSubString("td:eq(2) > div > div:eq(2) > a", 23, -2); + String title = node.text("td:eq(2) > div > div:eq(2) > a"); + String cover = node.src("td:eq(2) > div > div:eq(0) > img"); + if (cover == null) { + String temp = node.textWithSubstring("td:eq(2) > div > div:eq(0)", 19).split("~", 2)[0]; + cover = "https://exhentai.org/".concat(temp); + } + String update = node.textWithSubstring("td:eq(1)", 0, 10); + String author = StringUtils.match("\\[(.*?)\\]", title, 1); + title = title.replaceFirst("\\[.*?\\]\\s*", ""); + return new Comic(SourceManager.SOURCE_EXHENTAI, cid, title, cover, update, author); + } + }; + } + + @Override + public Request getInfoRequest(String cid) { + String url = StringUtils.format("https://exhentai.org/g/%s", cid); + return new Request.Builder().url(url).header("Cookie", "ipb_member_id=2145630; ipb_pass_hash=f883b5a9dd10234c9323957b96efbd8e;").build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String update = body.textWithSubstring("#gdd > table > tbody > tr:eq(0) > td:eq(1)", 0, 10); + String title = body.text("#gn"); + String intro = body.text("#gj"); + String author = body.text("#taglist > table > tbody > tr > td:eq(1) > div > a[id^=ta_artist]"); + String cover = body.attr("#gd1 > img", "src"); + comic.setInfo(title, cover, update, intro, author, true); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + Node body = new Node(html); + String length = body.textWithSplit("#gdd > table > tbody > tr:eq(5) > td:eq(1)", " ", 0); + int size = Integer.parseInt(length) % 40 == 0 ? Integer.parseInt(length) / 40 : Integer.parseInt(length) / 40 + 1; + for (int i = 0; i != size; ++i) { + list.add(0, new Chapter("Ch" + i, String.valueOf(i))); + } + return list; + } + + @Override + public Request getRecentRequest(int page) { + String url = StringUtils.format("https://exhentai.org/?page=%d", (page - 1)); + return new Request.Builder().url(url).header("Cookie", "ipb_member_id=2145630; ipb_pass_hash=f883b5a9dd10234c9323957b96efbd8e").build(); + } + + @Override + public List parseRecent(String html, int page) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list("table.itg > tbody > tr[class^=gtr]")) { + String cid = node.hrefWithSubString("td:eq(2) > div > div:eq(2) > a", 23, -2); + String title = node.text("td:eq(2) > div > div:eq(2) > a"); + String cover = node.src("td:eq(2) > div > div:eq(0) > img"); + if (cover == null) { + String temp = node.textWithSubstring("td:eq(2) > div > div:eq(0)", 19).split("~", 2)[0]; + cover = "https://exhentai.org/".concat(temp); + } + String update = node.textWithSubstring("td:eq(1)", 0, 10); + String author = StringUtils.match("\\[(.*?)\\]", title, 1); + title = title.replaceFirst("\\[.*?\\]\\s*", ""); + list.add(new Comic(SourceManager.SOURCE_EXHENTAI, cid, title, cover, update, author)); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("https://exhentai.org/g/%s?p=%s", cid, path); + return new Request.Builder().url(url).header("Cookie", "ipb_member_id=2145630; ipb_pass_hash=f883b5a9dd10234c9323957b96efbd8e;").build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + Node body = new Node(html); + int count = 0; + for (Node node : body.list("#gdt > div > div > a")) { + list.add(new ImageUrl(++count, node.attr("href"), true)); + } + return list; + } + + @Override + public Request getLazyRequest(String url) { + return new Request.Builder().url(url).header("Cookie", "ipb_member_id=2145630; ipb_pass_hash=f883b5a9dd10234c9323957b96efbd8e").build(); + } + + @Override + public String parseLazy(String html, String url) { + return new Node(html).attr("#img", "src"); + } +*/ +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/GuFeng.java b/app/src/main/java/com/hiroshi/cimoc/source/GuFeng.java new file mode 100644 index 00000000..c246dd10 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/GuFeng.java @@ -0,0 +1,148 @@ +package com.hiroshi.cimoc.source; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.LinkedList; +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by ZhiWen on 2019/02/25. + */ + +public class GuFeng extends MangaParser { + + public static final int TYPE = 25; + public static final String DEFAULT_TITLE = "古风漫画"; + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + public GuFeng(Source source) { + init(source, null); + } + + @Override + public Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException { + String url = ""; + if (page == 1) { + url = StringUtils.format("https://m.gufengmh8.com/search/?keywords=%s", + URLEncoder.encode(keyword, "UTF-8")); + } + return new Request.Builder() +// .addHeader("Referer", "https://www.gufengmh8.com/") +// .addHeader("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/12.0 Mobile/15A372 Safari/604.1") +// .addHeader("Host", "m.gufengmh8.com") + .url(url).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("div.UpdateList > div.itemBox")) { + @Override + protected Comic parse(Node node) { + + String cover = node.attr("div.itemImg > a > mip-img", "src"); + + String title = node.text("div.itemTxt > a"); + String cid = node.attr("div.itemTxt > a", "href").replace("https://m.gufengmh8.com/manhua/", ""); + cid = cid.substring(0, cid.length() - 1); + + String update = node.text("div.itemTxt > p:eq(3) > span.date"); + String author = node.text("div.itemTxt > p:eq(1)"); + + return new Comic(TYPE, cid, title, cover, update, author); + } + }; + } + + @Override + public Request getInfoRequest(String cid) { + String url = "https://m.gufengmh8.com/manhua/".concat(cid) + "/"; + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + String cover = body.src("#Cover > mip-img"); + String intro = body.text("div.comic-view.clearfix > p"); + String title = body.text("h1.title"); + + String update = body.text("div.pic > dl:eq(4) > dd"); + String author = body.text("div.pic > dl:eq(2) > dd"); + + // 连载状态 + boolean status = isFinish("连载"); + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + for (Node node : new Node(html).list("ul[id^=chapter-list] > li > a")) { + String title = node.text(); + String path = node.hrefWithSplit(2); + list.add(0, new Chapter(title, path)); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("https://m.gufengmh8.com/manhua/%s/%s.html", cid, path); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + String str = StringUtils.match("chapterImages = \\[(.*?)\\]", html, 1); + if (str != null) { + try { + String[] array = str.split(","); + String urlPrev = StringUtils.match("chapterPath = \"(.*?)\"", html, 1); + for (int i = 0; i != array.length; ++i) { + // 去掉首末两端的双引号 + String s = array[i].substring(1, array[i].length() - 1); + // http://res.gufengmh8.com/images/comic/159/316518/1519527843Efo9qfJOY9Jb_VP4.jpg + list.add(new ImageUrl(i + 1, "https://res.gufengmh8.com/" + urlPrev + s, false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + // 这里表示的是更新时间 + return new Node(html).text("div.pic > dl:eq(4) > dd"); + } + + @Override + public Headers getHeader() { + return Headers.of("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/HHAAZZ.java b/app/src/main/java/com/hiroshi/cimoc/source/HHAAZZ.java new file mode 100755 index 00000000..9ee0e201 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/HHAAZZ.java @@ -0,0 +1,335 @@ +package com.hiroshi.cimoc.source; + +import android.util.Pair; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaCategory; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by Hiroshi on 2016/7/26. + */ +public class HHAAZZ extends MangaParser { + + public static final int TYPE = 2; + public static final String DEFAULT_TITLE = "汗汗酷漫"; + + public HHAAZZ(Source source) { + init(source, new Category()); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) { + if (page == 1) { + final String url = "http://www.hhimm.com/comic/?act=search&st=".concat(keyword); + return new Request.Builder().url(url).build(); + } + return null; + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("div.cComicList > li")) { + @Override + protected Comic parse(Node node) { + final String cid = node.hrefWithSplit("a", 1); + final String title = node.attr("a", "title"); + final String cover = node.src("a > img"); + return new Comic(TYPE, cid, title, cover, null, null); + } + }; + } + + @Override + public String getUrl(String cid) { + return "http://hhaass.com/comic/".concat(cid); + } + + @Override + public Request getInfoRequest(String cid) { + final String url = StringUtils.format("http://www.hhimm.com/manhua/%s.html", cid); + return new Request.Builder().url(url).build(); + } + + private String title = ""; + + @Override + public void parseInfo(String html, Comic comic) { + final Node body = new Node(html); + final String cover = body.src("#about_style > img"); + int index = 0; + String update = "", intro = "", author = ""; + boolean status = false; + for (Node node : body.list("#about_kit > ul > li")) { + switch (index++) { + case 0: + title = node.getChild("h1").text().trim(); + break; + case 1: + author = node.text().replace("作者:", "").trim(); + break; + case 2: + String test = node.text().replace("状态:", "").trim(); + status = "连载" != test; + break; + case 4: + update = node.text().replace("更新:", "").trim(); + break; + case 7: + intro = node.text().replace("简介", "").trim().substring(1); + break; + default: + break; + } + } + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list(".cVolList > ul")) { + for (Node cnode : node.list("li")) { + String title = cnode.attr("a", "title").replace(this.title, "").trim(); + String path = cnode.href("a"); + list.add(new Chapter(title, path)); + } + } + return list; + } + + private String _path; + + @Override + public Request getImagesRequest(String cid, String path) { + String url = "http://www.hhimm.com".concat(path); + _path = path; + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + + //save page info + final String pathId = Node.splitHref(_path, 0); + final String pathS = Node.splitHref(_path, 4); + + Node body = new Node(html); + int i = 1; + for (Node node : body.list("#iPageHtm > a")) { + list.add(new ImageUrl(i, + StringUtils.format("http://www.hhimm.com/%s/%d.html?s=%s&d=0", pathId, i, pathS), + true)); + + i++; + } + + return list; + } + + @Override + public Request getLazyRequest(String url) { + return new Request.Builder().url(url).build(); + } + + @Override + public String parseLazy(String html, String url) { + Node body = new Node(html); + + // get img key + final String imgEleIds[] = {"img1021", "img2391", "img7652", "imgCurr"}; + String imgKey = null; + for (int i = 0; i < imgEleIds.length; i++) { + imgKey = body.attr("#".concat(imgEleIds[i]), "name"); + if (imgKey != null) break; + } + + String[] servers = body.attr("#hdDomain", "value").split("\\|"); + + //img key decode + if (imgKey != null) { + return servers[0] + unsuan(imgKey); + } + return null; + } + + //https://stackoverflow.com/questions/2946067/what-is-the-java-equivalent-to-javascripts-string-fromcharcode + public static String fromCharCode(int... codePoints) { + return new String(codePoints, 0, codePoints.length); + } + + private String unsuan(String s) { + final String sw = "44123.com|hhcool.com|hhimm.com"; + final String su = "www.hhimm.com"; + boolean b = false; + + for (int i = 0; i < sw.split("|").length; i++) { + if (su.indexOf(sw.split("|")[i]) > -1) { + b = true; + break; + } + } + if (!b) + return ""; + + final String x = s.substring(s.length() - 1); + final String w = "abcdefghijklmnopqrstuvwxyz"; + int xi = w.indexOf(x) + 1; + final String sk = s.substring(s.length() - xi - 12, s.length() - xi - 1); + s = s.substring(0, s.length() - xi - 12); + String k = sk.substring(0, sk.length() - 1); + String f = sk.substring(sk.length() - 1); + + for (int i = 0; i < k.length(); i++) { + s = s.replace(k.substring(i, i + 1), Integer.toString(i)); + } + String[] ss = s.split(f); + s = ""; + for (int i = 0; i < ss.length; i++) { + s += fromCharCode(Integer.parseInt(ss[i])); + } + return s; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).textWithSubstring("div.main > div > div.pic > div.con > p:eq(5)", 5); + } + + @Override + public List parseCategory(String html, int page) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list("li.clearfix > a.pic")) { + String cid = node.hrefWithSplit(1); + String title = node.text("div.con > h3"); + String cover = node.src("img"); + String update = node.textWithSubstring("div.con > p > span", 0, 10); + String author = node.text("div.con > p:eq(1)"); + list.add(new Comic(TYPE, cid, title, cover, update, author)); + } + return list; + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://hhaass.com/"); + } + + private static class Category extends MangaCategory { + + @Override + public String getFormat(String... args) { + if (!"".equals(args[CATEGORY_SUBJECT])) { + return StringUtils.format("http://hhaass.com/lists/%s/%%d", args[CATEGORY_SUBJECT]); + } else if (!"".equals(args[CATEGORY_AREA])) { + return StringUtils.format("http://hhaass.com/lists/%s/%%d", args[CATEGORY_AREA]); + } else if (!"".equals(args[CATEGORY_READER])) { + return StringUtils.format("http://hhaass.com/duzhequn/%s/%%d", args[CATEGORY_PROGRESS]); + } else if (!"".equals(args[CATEGORY_PROGRESS])) { + return StringUtils.format("http://hhaass.com/lianwan/%s/%%d", args[CATEGORY_PROGRESS]); + } else { + return "http://hhaass.com/dfcomiclist_%d.htm"; + } + } + + @Override + protected List> getSubject() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("萌系", "1")); + list.add(Pair.create("搞笑", "2")); + list.add(Pair.create("格斗", "3")); + list.add(Pair.create("科幻", "4")); + list.add(Pair.create("剧情", "5")); + list.add(Pair.create("侦探", "6")); + list.add(Pair.create("竞技", "7")); + list.add(Pair.create("魔法", "8")); + list.add(Pair.create("神鬼", "9")); + list.add(Pair.create("校园", "10")); + list.add(Pair.create("惊栗", "11")); + list.add(Pair.create("厨艺", "12")); + list.add(Pair.create("伪娘", "13")); + list.add(Pair.create("图片", "14")); + list.add(Pair.create("冒险", "15")); + list.add(Pair.create("耽美", "21")); + list.add(Pair.create("经典", "22")); + list.add(Pair.create("亲情", "25")); + return list; + } + + @Override + protected boolean hasArea() { + return true; + } + + @Override + protected List> getArea() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("大陆", "19")); + list.add(Pair.create("香港", "20")); + list.add(Pair.create("欧美", "23")); + list.add(Pair.create("日文", "24")); + return list; + } + + @Override + protected boolean hasReader() { + return true; + } + + @Override + protected List> getReader() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("少年", "1")); + list.add(Pair.create("少女", "2")); + list.add(Pair.create("青年", "3")); + return list; + } + + @Override + protected boolean hasProgress() { + return true; + } + + @Override + protected List> getProgress() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("连载", "1")); + list.add(Pair.create("完结", "2")); + return list; + } + + } + +} + diff --git a/app/src/main/java/com/hiroshi/cimoc/source/HHSSEE.java b/app/src/main/java/com/hiroshi/cimoc/source/HHSSEE.java new file mode 100644 index 00000000..95c51120 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/HHSSEE.java @@ -0,0 +1,247 @@ +package com.hiroshi.cimoc.source; + +import android.util.Pair; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaCategory; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by Hiroshi on 2016/10/1. + */ + +public class HHSSEE extends MangaParser { + + public static final int TYPE = 7; + public static final String DEFAULT_TITLE = "汗汗漫画"; + + public HHSSEE(Source source) { + init(source, new Category()); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) { + if (page == 1) { + String url = "http://www.hhmmoo.com/comic/?act=search&st=".concat(keyword); + return new Request.Builder().url(url).build(); + } + return null; + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("#list > div.cComicList > li > a")) { + @Override + protected Comic parse(Node node) { + String cid = node.hrefWithSubString(7, -6); + String title = node.text(); + String cover = node.src("img"); + return new Comic(TYPE, cid, title, cover, null, null); + } + }; + } + + @Override + public String getUrl(String cid) { + return StringUtils.format("http://www.hhmmoo.com/manhua%s.html", cid); + } + + @Override + public Request getInfoRequest(String cid) { + String url = StringUtils.format("http://www.hhmmoo.com/manhua%s.html", cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String title = body.text("#about_kit > ul > li:eq(0) > h1"); + String cover = body.src("#about_style > img"); + String update = body.textWithSubstring("#about_kit > ul > li:eq(4)", 3); + if (update != null) { + String[] args = update.split("\\D"); + update = StringUtils.format("%4d-%02d-%02d", Integer.parseInt(args[0]), Integer.parseInt(args[1]), Integer.parseInt(args[2])); + } + String author = body.textWithSubstring("#about_kit > ul > li:eq(1)", 3); + String intro = body.textWithSubstring("#about_kit > ul > li:eq(7)", 3); + boolean status = isFinish(body.text("#about_kit > ul > li:eq(2)")); + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new ArrayList<>(); + Node body = new Node(html); + String name = body.text("#about_kit > ul > li:eq(0) > h1"); + for (Node node : body.list("#permalink > div.cVolList > ul.cVolUl > li > a")) { + String title = node.text(); + title = title.replaceFirst(name, "").trim(); + String[] array = StringUtils.match("/page(\\d+).*s=(\\d+)", node.attr("href"), 1, 2); + //String path = array != null ? array[0].concat(" ").concat(array[1]) : ""; + String path = array != null ? array[0].concat("-").concat(array[1]) : ""; + list.add(new Chapter(title.trim(), path)); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String[] array = path.split("-"); + String url = StringUtils.format("http://www.hhmmoo.com/page%s/1.html?s=%s", array[0], array[1]); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new ArrayList<>(); + Node body = new Node(html); + int page = Integer.parseInt(body.attr("#hdPageCount", "value")); + String path = body.attr("#hdVolID", "value"); + String server = body.attr("#hdS", "value"); + for (int i = 1; i <= page; ++i) { + list.add(new ImageUrl(i, StringUtils.format("http://www.hhmmoo.com/page%s/%d.html?s=%s", path, i, server), true)); + } + return list; + } + + @Override + public Request getLazyRequest(String url) { + return new Request.Builder().url(url).build(); + } + + @Override + public String parseLazy(String html, String url) { + Node body = new Node(html); + String server = body.attr("#hdDomain", "value"); + if (server != null) { + server = server.split("\\|")[0]; + String name = body.attr("#iBodyQ > img", "name"); + String result = unsuan(name).substring(1); + return server.concat(result); + } + return null; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + String update = new Node(html).textWithSubstring("#about_kit > ul > li:eq(4)", 3); + if (update != null) { + String[] args = update.split("\\D"); + update = StringUtils.format("%4d-%02d-%02d", Integer.parseInt(args[0]), Integer.parseInt(args[1]), Integer.parseInt(args[2])); + } + return update; + } + + private String unsuan(String str) { + int num = str.length() - str.charAt(str.length() - 1) + 'a'; + String code = str.substring(num - 13, num - 2); + String cut = code.substring(code.length() - 1); + str = str.substring(0, num - 13); + code = code.substring(0, code.length() - 1); + for (int i = 0; i < code.length(); i++) { + str = str.replace(code.charAt(i), (char) ('0' + i)); + } + StringBuilder builder = new StringBuilder(); + String[] array = str.split(cut); + for (int i = 0; i != array.length; ++i) { + builder.append((char) Integer.parseInt(array[i])); + } + return builder.toString(); + } + + @Override + public List parseCategory(String html, int page) { + List list = new ArrayList<>(); + Node body = new Node(html); + for (Node node : body.list("#list > div.cComicList > li > a")) { + String cid = node.hrefWithSubString(7, -6); + String title = node.attr("title"); + String cover = node.src("img"); + list.add(new Comic(TYPE, cid, title, cover, null, null)); + } + return list; + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://www.hhmmoo.com"); + } + + private static class Category extends MangaCategory { + @Override + public String getFormat(String... args) { + if (!"".equals(args[CATEGORY_SUBJECT])) { + return StringUtils.format("http://www.hhmmoo.com/comic/class_%s/%%d.html", args[CATEGORY_SUBJECT]); + } else if (!"".equals(args[CATEGORY_AREA])) { + return StringUtils.format("http://www.hhmmoo.com/comic/class_%s/%%d.html", args[CATEGORY_AREA]); + } else { + return "http://www.hhmmoo.com/comic/%d.html"; + } + } + + @Override + protected List> getSubject() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("萌系", "1")); + list.add(Pair.create("搞笑", "2")); + list.add(Pair.create("格斗", "3")); + list.add(Pair.create("科幻", "4")); + list.add(Pair.create("剧情", "5")); + list.add(Pair.create("侦探", "6")); + list.add(Pair.create("竞技", "7")); + list.add(Pair.create("魔法", "8")); + list.add(Pair.create("神鬼", "9")); + list.add(Pair.create("校园", "10")); + list.add(Pair.create("惊栗", "11")); + list.add(Pair.create("厨艺", "12")); + list.add(Pair.create("伪娘", "13")); + list.add(Pair.create("冒险", "15")); + list.add(Pair.create("小说", "19")); + list.add(Pair.create("耽美", "21")); + list.add(Pair.create("经典", "22")); + list.add(Pair.create("亲情", "25")); + return list; + } + + @Override + protected boolean hasArea() { + return true; + } + + @Override + protected List> getArea() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("香港", "20")); + list.add(Pair.create("欧美", "23")); + list.add(Pair.create("日文", "24")); + return list; + } + + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/Hhxxee.java b/app/src/main/java/com/hiroshi/cimoc/source/Hhxxee.java new file mode 100644 index 00000000..a4b057df --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/Hhxxee.java @@ -0,0 +1,175 @@ +package com.hiroshi.cimoc.source; + +import android.util.Pair; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaCategory; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.DecryptionUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.Request; +import okhttp3.RequestBody; + +/** + * Created by FEILONG on 2017/12/21. + */ + +public class Hhxxee extends MangaParser { + + public static final int TYPE = 59; + public static final String DEFAULT_TITLE = "997700"; + + public Hhxxee(Source source) { + init(source, null); + } + + private static final String serverstr = "http://20.125084.com/dm01/|http://20.125084.com/dm02/|http://20.125084.com/dm03/|http://20.125084.com/dm04/|http://20.125084.com/dm05/|http://20.125084.com/dm06/|http://20.125084.com/dm07/|http://20.125084.com/dm08/|http://20.125084.com/dm09/|http://20.125084.com/dm10/|http://20.125084.com/dm11/|http://20.125084.com/dm12/|http://20.125084.com/dm13/|http://20.125084.com/dm14/|http://20.125084.com/dm15/|http://20.125084.com/dm16/"; + private static final String[] servers = serverstr.split("\\|"); + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException { + String url = ""; + if (page == 1) + url = "http://99770.hhxxee.com/search/s.aspx"; + RequestBody requestBodyPost = new FormBody.Builder() + .add("tbSTxt", keyword) + .build(); + return new Request.Builder().url(url) + .post(requestBodyPost).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list(".cInfoItem")) { + @Override + protected Comic parse(Node node) { + String cid = node.href(".cListTitle > a").substring("http://99770.hhxxee.com/comic/".length()); + String title = node.text(".cListTitle > span"); + title = title.substring(1, title.length() - 1); + String cover = node.src(".cListSlt > img"); + String update = node.text(".cListh2 > span").substring(8); + String author = node.text(".cl1_2").substring(3); + return new Comic(TYPE, cid, title, cover, update, author); + } + }; + } + + @Override + public String getUrl(String cid) { + return "http://99770.hhxxee.com/comic/".concat(cid); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("99770.hhxxee.com","(\\d+)$")); + } + + @Override + public Request getInfoRequest(String cid) { + String url = "http://99770.hhxxee.com/comic/".concat(cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + String title = body.text(".cTitle"); + String cover = body.src(".cDefaultImg > img"); + String update = ""; + String author = ""; + String intro = body.text(".cCon"); + boolean status = false; + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + for (Node node : new Node(html).list("#subBookListAct > div")) { + String title = node.text("a"); + String path = node.hrefWithSplit("a", 2); + list.add(new Chapter(title, path)); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("http://99770.hhxxee.com/comic/%s/%s/", cid, path); + return new Request.Builder().url(url).build(); + } + + private int getPictureServers(String url) { + return Integer.parseInt(StringUtils.match("ok\\-comic(\\d+)", url, 1)) - 1; + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + String str = StringUtils.match("var sFiles=\"(.*?)\"", html, 1); + if (str != null) { + try { + String[] array = str.split("\\|"); + for (int i = 0; i != array.length; ++i) { + list.add(new ImageUrl(i + 1, servers[getPictureServers(array[i])] + array[i], false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).text("div.book-detail > div.cont-list > dl:eq(2) > dd"); + } + + @Override + public List parseCategory(String html, int page) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list("li > a")) { + String cid = node.hrefWithSplit(1); + String title = node.text("h3"); + String cover = node.attr("div > img", "data-src"); + String update = node.text("dl:eq(5) > dd"); + String author = node.text("dl:eq(2) > dd"); + list.add(new Comic(TYPE, cid, title, cover, update, author)); + } + return list; + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://99770.hhxxee.com"); + } + + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/IKanman.java b/app/src/main/java/com/hiroshi/cimoc/source/IKanman.java new file mode 100644 index 00000000..4510b91a --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/IKanman.java @@ -0,0 +1,362 @@ +package com.hiroshi.cimoc.source; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.DecryptionUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by Hiroshi on 2016/7/8. + */ +public class IKanman extends MangaParser { + + // TODO 实测联通4G网络无法使用看漫画 + + public static final int TYPE = 0; + public static final String DEFAULT_TITLE = "漫画柜"; + + private String referer = ""; + + public IKanman(Source source) { + init(source, null); +// init(source, new Category()); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, false); + } + + @Override + public Request getSearchRequest(String keyword, int page) { + String url = StringUtils.format("https://www.manhuagui.com/s/%s_p%d.html", keyword, page); + return new Request.Builder() + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19") + .url(url) + .build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("li.cf")) { + @Override + protected Comic parse(Node node) { + String cid = node.hrefWithSplit("a.bcover", 1); + String title = node.text(".book-detail > dl > dt > a"); + String cover = node.attr("a.bcover > img", "src"); +// String update = node.text("dl:eq(5) > dd"); +// String author = node.text("dl:eq(2) > dd"); + return new Comic(TYPE, cid, title, cover, "", ""); + } + }; + } + + @Override + public String getUrl(String cid) { + return "https://tw.manhuagui.com/comic/".concat(cid); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("www.manhuagui.com")); + filter.add(new UrlFilter("tw.manhuagui.com")); + filter.add(new UrlFilter("m.manhuagui.com")); + } + + @Override + public Request getInfoRequest(String cid) { + String url = "https://tw.manhuagui.com/comic/".concat(cid).concat("/"); + return new Request.Builder() + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19") + .url(url) + .build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String title = body.text("div.book-title > h1"); + String cover = body.src("p.hcover > img"); + String update = body.text("div.chapter-bar > span.fr > span:eq(1)"); + String author = body.attr("ul.detail-list > li:eq(1) > span:eq(1) > a", "title"); + String intro = body.text("#intro-cut"); + boolean status = isFinish(body.text("div.chapter-bar > span.fr > span:eq(0)")); + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + Node body = new Node(html); + String baseText = body.id("__VIEWSTATE").attr("value"); + if (!StringUtils.isEmpty(baseText)) { + body = new Node(DecryptionUtils.LZ64Decrypt(baseText)); + } + for (Node node : body.list("div.chapter-list")) { + List uls = node.list("ul"); + Collections.reverse(uls); + for (Node ul : uls) { + for (Node li : ul.list("li > a")) { + String title = li.attr("title"); + String path = li.hrefWithSplit(2); + list.add(new Chapter(title, path)); + } + } + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("https://tw.manhuagui.com/comic/%s/%s.html", cid, path); + referer = url; + return new Request.Builder() + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19") +// .addHeader("Referer", StringUtils.format("https://m.manhuagui.com/comic/%s/%s.html", cid, path)) + .url(url) + .build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + String packed = StringUtils.match("\\(function\\(p,a,c,k,e,d\\).*?0,\\{\\}\\)\\)", html, 0); + if (packed != null) { + try { + String replaceable = StringUtils.split(packed, ",", -3); + String fake = StringUtils.split(replaceable, "'", 1); + String real = DecryptionUtils.LZ64Decrypt(fake); + packed = packed.replace(replaceable, StringUtils.format("'%s'.split('|')", real)); + String result = DecryptionUtils.evalDecrypt(packed); + + String jsonString = result.substring(12, result.length() - 12); + JSONObject object = new JSONObject(jsonString); + String chapterId = object.getString("cid"); + String path = object.getString("path"); +// String md5 = object.getJSONObject("sl").getString("md5"); + String e = object.getJSONObject("sl").getString("e"); + String m = object.getJSONObject("sl").getString("m"); + JSONArray array = object.getJSONArray("files"); + for (int i = 0; i != array.length(); ++i) { + String url = StringUtils.format("https://i.hamreus.com%s%s?e=%s&m=%s", path, array.getString(i), e, m); + list.add(new ImageUrl(i + 1, url, false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).text("div.chapter-bar > span.fr > span:eq(1)"); + } + + @Override + public List parseCategory(String html, int page) { + List list = new ArrayList<>(); + Node body = new Node(html); + for (Node node : body.list("#AspNetPager1 > span.current")) { + try { + if (Integer.parseInt(node.text()) < page) { + return list; + } + } catch (NumberFormatException e) { + e.printStackTrace(); + } + } + for (Node node : body.list("#contList > li")) { + String cid = node.hrefWithSplit("a", 1); + String title = node.attr("a", "title"); + String cover = node.src("a > img"); + if (StringUtils.isEmpty(cover)) { + cover = node.attr("a > img", "data-src"); + } + String update = node.textWithSubstring("span.updateon", 4, 14); + list.add(new Comic(TYPE, cid, title, cover, update, null)); + } + return list; + } + + @Override + public Headers getHeader() { +// return Headers.of("Referer", "https://tw.manhuagui.com/comic/30449/408812.html", + return Headers.of("Referer", referer, + "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"); + } + +// private static class Category extends MangaCategory { +// +// @Override +// public boolean isComposite() { +// return true; +// } +// +// @Override +// public String getFormat(String... args) { +// String path = args[CATEGORY_AREA].concat(" ").concat(args[CATEGORY_SUBJECT]).concat(" ").concat(args[CATEGORY_READER]) +// .concat(" ").concat(args[CATEGORY_YEAR]).concat(" ").concat(args[CATEGORY_PROGRESS]).trim(); +// path = path.replaceAll("\\s+", "_"); +// return StringUtils.format("https://www.manhuagui.com/list/%s/%s_p%%d.html", path, args[CATEGORY_ORDER]); +// } +// +// @Override +// public List> getSubject() { +// List> list = new ArrayList<>(); +// list.add(Pair.create("全部", "")); +// list.add(Pair.create("热血", "rexue")); +// list.add(Pair.create("冒险", "maoxian")); +// list.add(Pair.create("魔幻", "mohuan")); +// list.add(Pair.create("神鬼", "shengui")); +// list.add(Pair.create("搞笑", "gaoxiao")); +// list.add(Pair.create("萌系", "mengxi")); +// list.add(Pair.create("爱情", "aiqing")); +// list.add(Pair.create("科幻", "kehuan")); +// list.add(Pair.create("魔法", "mofa")); +// list.add(Pair.create("格斗", "gedou")); +// list.add(Pair.create("武侠", "wuxia")); +// list.add(Pair.create("机战", "jizhan")); +// list.add(Pair.create("战争", "zhanzheng")); +// list.add(Pair.create("竞技", "jingji")); +// list.add(Pair.create("体育", "tiyu")); +// list.add(Pair.create("校园", "xiaoyuan")); +// list.add(Pair.create("生活", "shenghuo")); +// list.add(Pair.create("励志", "lizhi")); +// list.add(Pair.create("历史", "lishi")); +// list.add(Pair.create("伪娘", "weiniang")); +// list.add(Pair.create("宅男", "zhainan")); +// list.add(Pair.create("腐女", "funv")); +// list.add(Pair.create("耽美", "danmei")); +// list.add(Pair.create("百合", "baihe")); +// list.add(Pair.create("后宫", "hougong")); +// list.add(Pair.create("治愈", "zhiyu")); +// list.add(Pair.create("美食", "meishi")); +// list.add(Pair.create("推理", "tuili")); +// list.add(Pair.create("悬疑", "xuanyi")); +// list.add(Pair.create("恐怖", "kongbu")); +// list.add(Pair.create("四格", "sige")); +// list.add(Pair.create("职场", "zhichang")); +// list.add(Pair.create("侦探", "zhentan")); +// list.add(Pair.create("社会", "shehui")); +// list.add(Pair.create("音乐", "yinyue")); +// list.add(Pair.create("舞蹈", "wudao")); +// list.add(Pair.create("杂志", "zazhi")); +// list.add(Pair.create("黑道", "heidao")); +// return list; +// } +// +// @Override +// public boolean hasArea() { +// return true; +// } +// +// @Override +// public List> getArea() { +// List> list = new ArrayList<>(); +// list.add(Pair.create("全部", "")); +// list.add(Pair.create("日本", "japan")); +// list.add(Pair.create("港台", "hongkong")); +// list.add(Pair.create("其它", "other")); +// list.add(Pair.create("欧美", "europe")); +// list.add(Pair.create("内地", "china")); +// list.add(Pair.create("韩国", "korea")); +// return list; +// } +// +// @Override +// public boolean hasReader() { +// return true; +// } +// +// @Override +// public List> getReader() { +// List> list = new ArrayList<>(); +// list.add(Pair.create("全部", "")); +// list.add(Pair.create("少女", "shaonv")); +// list.add(Pair.create("少年", "shaonian")); +// list.add(Pair.create("青年", "qingnian")); +// list.add(Pair.create("儿童", "ertong")); +// list.add(Pair.create("通用", "tongyong")); +// return list; +// } +// +// @Override +// public boolean hasYear() { +// return true; +// } +// +// @Override +// public List> getYear() { +// List> list = new ArrayList<>(); +// list.add(Pair.create("全部", "")); +// list.add(Pair.create("2017年", "2017")); +// list.add(Pair.create("2016年", "2016")); +// list.add(Pair.create("2015年", "2015")); +// list.add(Pair.create("2014年", "2014")); +// list.add(Pair.create("2013年", "2013")); +// list.add(Pair.create("2012年", "2012")); +// list.add(Pair.create("2011年", "2011")); +// list.add(Pair.create("2010年", "2010")); +// list.add(Pair.create("00年代", "200x")); +// list.add(Pair.create("90年代", "199x")); +// list.add(Pair.create("80年代", "198x")); +// list.add(Pair.create("更早", "197x")); +// return list; +// } +// +// @Override +// public boolean hasProgress() { +// return true; +// } +// +// @Override +// public List> getProgress() { +// List> list = new ArrayList<>(); +// list.add(Pair.create("全部", "")); +// list.add(Pair.create("连载", "lianzai")); +// list.add(Pair.create("完结", "wanjie")); +// return list; +// } +// +// @Override +// protected boolean hasOrder() { +// return true; +// } +// +// @Override +// protected List> getOrder() { +// List> list = new ArrayList<>(); +// list.add(Pair.create("更新", "update")); +// list.add(Pair.create("发布", "index")); +// list.add(Pair.create("人气", "view")); +// list.add(Pair.create("评分", "rate")); +// return list; +// } +// +// } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/Locality.java b/app/src/main/java/com/hiroshi/cimoc/source/Locality.java new file mode 100644 index 00000000..96bf7c48 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/Locality.java @@ -0,0 +1,86 @@ +package com.hiroshi.cimoc.source; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.SearchIterator; + +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by Hiroshi on 2017/5/21. + */ + +public class Locality extends MangaParser { + + public static final int TYPE = -2; + public static final String DEFAULT_TITLE = "本地漫画"; + + public Locality() { + mTitle = DEFAULT_TITLE; + } + + @Override + public Request getSearchRequest(String keyword, int page) { + return null; + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + return null; + } + + @Override + public String getUrl(String cid) { + return null; + } + + @Override + public Request getInfoRequest(String cid) { + return null; + } + + @Override + public void parseInfo(String html, Comic comic) { + } + + @Override + public List parseChapter(String html) { + return null; + } + + @Override + public Request getImagesRequest(String cid, String path) { + return null; + } + + @Override + public List parseImages(String html) { + return null; + } + + @Override + public Request getCheckRequest(String cid) { + return null; + } + + @Override + public String parseCheck(String html) { + return null; + } + + @Override + public List parseCategory(String html, int page) { + return null; + } + + @Override + public Headers getHeader() { + return null; + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/MH50.java b/app/src/main/java/com/hiroshi/cimoc/source/MH50.java new file mode 100644 index 00000000..a46584fa --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/MH50.java @@ -0,0 +1,385 @@ +package com.hiroshi.cimoc.source; + +import android.util.Pair; + +import com.alibaba.fastjson.JSONArray; +import com.google.common.collect.Lists; +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaCategory; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.DecryptionUtils; +import com.hiroshi.cimoc.utils.HttpUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +import javax.annotation.Nullable; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by WinterWhisper on 2019/2/25. + */ +public class MH50 extends MangaParser { + + public static final int TYPE = 80; + public static final String DEFAULT_TITLE = "漫画堆"; + public static final String website = "https://www.manhuabei.com"; + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + public MH50(Source source) { + init(source, new Category()); + } + + @Override + public Request getSearchRequest(String keyword, int page) { + String url = StringUtils.format("%s/search/?keywords=%s&page=%d", website, keyword, page); + return HttpUtils.getSimpleMobileRequest(url); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("ul.list_con_li > li.list-comic")) { + @Override + protected Comic parse(Node node) { + String cid = node.hrefWithSplit("a", 1); + String title = node.attr("a","title"); + String cover = node.src("img"); + if (cover.startsWith("//")) cover = "https:" + cover; + String update = node.text("p.newPage"); + String author = node.text("p.auth"); + return new Comic(TYPE, cid, title, cover, update, author); + } + }; + } + + @Override + public String getUrl(String cid) { + return StringUtils.format("%s/manhua/%s/", website, cid); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter(website)); + } + + @Override + public Request getInfoRequest(String cid) { + String url = StringUtils.format("%s/manhua/%s/", website, cid); + return HttpUtils.getSimpleMobileRequest(url); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String intro = body.text("p.comic_deCon_d"); + String title = body.text("div.comic_deCon > h1"); + String cover = body.src("div.comic_i_img > img"); + if (cover.startsWith("//")) cover = "https:" + cover; + String author = body.text("ul.comic_deCon_liO > li.eq(0)"); + String update = ""; + boolean status = isFinish(body.text("ul.comic_deCon_liO > li.eq(1)")); + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list("div.zj_list_con > ul > li")) { + String title = node.attr("a", "title"); + String path = StringUtils.split(node.href("a"), "/", 3); + list.add(new Chapter(title, path)); + } + + return Lists.reverse(list); + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("%s/manhua/%s/%s", website, cid, path); + return HttpUtils.getSimpleMobileRequest(url); + } + + private final String[] server = {"https://mhcdn.manhuazj.com"}; + + @Nullable + private String decrypt(String code) { + String key = "KA58ZAQ321oobbG8"; + String iv = "A1B2C3DEF1G321o8"; + try { + return DecryptionUtils.aesDecrypt(code, key, iv); + } catch (Exception e) { + return null; + } + } + + //根据文件名获取图片url,参考common.js中getChapterImage函数 + private String getImageUrlByKey(String key, String domain, String chapter) { + if (Pattern.matches("\\^https?://(images.dmzj.com|imgsmall.dmzj.com)/i", key)) { + try { + return domain + "/showImage.php?url=" + URLEncoder.encode(key, "utf-8"); + } catch (Exception e) { + return null; + } + } + if (Pattern.matches("\\^[a-z]//i", key)) { + try { + return domain + "/showImage.php?url=" + URLEncoder.encode("https://images.dmzj.com/" + key, "utf-8"); + } catch (Exception e) { + return null; + } + } + if (key.startsWith("http") || key.startsWith("ftp")) return key; + return domain + "/" + chapter + key; + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + + //该章节的所有图片url,aes加密 + String arrayStringCode = decrypt(StringUtils.match("var chapterImages =[\\s\\n]*\"(.*?)\";", html, 1)); + if (arrayStringCode == null) return list; + JSONArray imageList = JSONArray.parseArray(arrayStringCode); + + //章节url,用于拼接最终的图片url + String chapterPath = StringUtils.match("var chapterPath = \"([\\s\\S]*?)\";", html, 1); + + int imageListSize = imageList.size(); + for (int i = 0; i != imageListSize; ++i) { + String key = imageList.getString(i); + String imageUrl = getImageUrlByKey(key, server[0], chapterPath); + + if(imageUrl.indexOf("images.dmzj.com") >= 0) + imageUrl = "https://img01.eshanyao.com/showImage.php?url=" + imageUrl; + + list.add(new ImageUrl(i + 1, imageUrl, false)); + } + + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).text(".Introduct_Sub > .sub_r > .txtItme:eq(4)"); + } + + @Override + public List parseCategory(String html, int page) { + List list = new LinkedList<>(); + Node body = new Node(html); + int totalPage = Integer.parseInt(body.attr("#total-page", "value")); + if (page <= totalPage) { + for (Node node : body.list("#comic-items > li")) { + String cid = node.hrefWithSplit("a.ImgA", 1); + String title = node.text("a.txtA"); + String cover = node.src("a.ImgA img"); + if (cover.startsWith("//")) cover = "https:" + cover; + String update = node.text(".info"); + list.add(new Comic(TYPE, cid, title, cover, update, null)); + } + } + return list; + } + + private static class Category extends MangaCategory { + + @Override + public boolean isComposite() { + return true; + } + + @Override + public String getFormat(String... args) { + String path = args[CATEGORY_SUBJECT].concat(" ").concat(args[CATEGORY_AREA]).concat(" ") + .concat(args[CATEGORY_READER]).concat(" ").concat(args[CATEGORY_YEAR]).concat(" ") + .concat(args[CATEGORY_PROGRESS]).trim(); + String finalPath; + if (path.isEmpty()) { + finalPath = StringUtils.format("%s/list/", website); + } else { + finalPath = StringUtils.format("%s/list/%s/?page=%%d", website, path).replaceAll("\\s+", "-"); + } + return finalPath; + } + + @Override + public List> getSubject() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("热血", "rexue")); + list.add(Pair.create("冒险", "maoxian")); + list.add(Pair.create("玄幻", "xuanhuan")); + list.add(Pair.create("搞笑", "gaoxiao")); + list.add(Pair.create("恋爱", "lianai")); + list.add(Pair.create("宠物", "chongwu")); + list.add(Pair.create("新作", "xinzuo")); + list.add(Pair.create("神魔", "shenmo")); + list.add(Pair.create("竞技", "jingji")); + list.add(Pair.create("穿越", "chuanyue")); + list.add(Pair.create("漫改", "mangai")); + list.add(Pair.create("霸总", "bazong")); + list.add(Pair.create("都市", "dushi")); + list.add(Pair.create("武侠", "wuxia")); + list.add(Pair.create("社会", "shehui")); + list.add(Pair.create("古风", "gufeng")); + list.add(Pair.create("恐怖", "kongbu")); + list.add(Pair.create("东方", "dongfang")); + list.add(Pair.create("格斗", "gedou")); + list.add(Pair.create("魔法", "mofa")); + list.add(Pair.create("轻小说", "qingxiaoshuo")); + list.add(Pair.create("魔幻", "mohuan")); + list.add(Pair.create("生活", "shenghuo")); + list.add(Pair.create("欢乐向", "huanlexiang")); + list.add(Pair.create("励志", "lizhi")); + list.add(Pair.create("音乐舞蹈", "yinyuewudao")); + list.add(Pair.create("科幻", "kehuan")); + list.add(Pair.create("美食", "meishi")); + list.add(Pair.create("节操", "jiecao")); + list.add(Pair.create("神鬼", "shengui")); + list.add(Pair.create("爱情", "aiqing")); + list.add(Pair.create("校园", "xiaoyuan")); + list.add(Pair.create("治愈", "zhiyu")); + list.add(Pair.create("奇幻", "qihuan")); + list.add(Pair.create("仙侠", "xianxia")); + list.add(Pair.create("运动", "yundong")); + list.add(Pair.create("动作", "dongzuo")); + list.add(Pair.create("日更", "rigeng")); + list.add(Pair.create("历史", "lishi")); + list.add(Pair.create("推理", "tuili")); + list.add(Pair.create("悬疑", "xuanyi")); + list.add(Pair.create("修真", "xiuzhen")); + list.add(Pair.create("游戏", "youxi")); + list.add(Pair.create("战争", "zhanzheng")); + list.add(Pair.create("后宫", "hougong")); + list.add(Pair.create("职场", "zhichang")); + list.add(Pair.create("四格", "sige")); + list.add(Pair.create("性转换", "xingzhuanhuan")); + list.add(Pair.create("伪娘", "weiniang")); + list.add(Pair.create("颜艺", "yanyi")); + list.add(Pair.create("真人", "zhenren")); + list.add(Pair.create("杂志", "zazhi")); + list.add(Pair.create("侦探", "zhentan")); + list.add(Pair.create("萌系", "mengxi")); + list.add(Pair.create("耽美", "danmei")); + list.add(Pair.create("百合", "baihe")); + list.add(Pair.create("西方魔幻", "xifangmohuan")); + list.add(Pair.create("机战", "jizhan")); + list.add(Pair.create("宅系", "zhaixi")); + list.add(Pair.create("忍者", "renzhe")); + list.add(Pair.create("萝莉", "luoli")); + list.add(Pair.create("异世界", "yishijie")); + list.add(Pair.create("吸血", "xixie")); + list.add(Pair.create("其他", "qita")); + return list; + } + + @Override + public boolean hasArea() { + return true; + } + + @Override + public List> getArea() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("日本", "riben")); + list.add(Pair.create("大陆", "dalu")); + list.add(Pair.create("香港", "hongkong")); + list.add(Pair.create("台湾", "taiwan")); + list.add(Pair.create("欧美", "oumei")); + list.add(Pair.create("韩国", "hanguo")); + list.add(Pair.create("其他", "qita")); + return list; + } + + @Override + public boolean hasReader() { + return true; + } + + @Override + public List> getReader() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("儿童漫画", "ertong")); + list.add(Pair.create("少年漫画", "shaonian")); + list.add(Pair.create("少女漫画", "shaonv")); + list.add(Pair.create("青年漫画", "qingnian")); + return list; + } + + @Override + public boolean hasProgress() { + return true; + } + + @Override + public List> getProgress() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("连载", "lianzai")); + list.add(Pair.create("完结", "wanjie")); + return list; + } + + @Override + protected boolean hasYear() { + return true; + } + + @Override + protected List> getYear() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("2000年前", "2000nianqian")); + list.add(Pair.create("2001年", "2001nian")); + list.add(Pair.create("2002年", "2002nian")); + list.add(Pair.create("2003年", "2003nian")); + list.add(Pair.create("2004年", "2004nian")); + list.add(Pair.create("2005年", "2005nian")); + list.add(Pair.create("2006年", "2006nian")); + list.add(Pair.create("2007年", "2007nian")); + list.add(Pair.create("2008年", "2008nian")); + list.add(Pair.create("2009年", "2009nian")); + list.add(Pair.create("2010年", "2010nian")); + list.add(Pair.create("2011年", "2011nian")); + list.add(Pair.create("2012年", "2012nian")); + list.add(Pair.create("2013年", "2013nian")); + list.add(Pair.create("2014年", "2014nian")); + list.add(Pair.create("2015年", "2015nian")); + list.add(Pair.create("2016年", "2016nian")); + list.add(Pair.create("2017年", "2017nian")); + list.add(Pair.create("2018年", "2018nian")); + return list; + } + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", website); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/MH517.java b/app/src/main/java/com/hiroshi/cimoc/source/MH517.java new file mode 100644 index 00000000..62f59bba --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/MH517.java @@ -0,0 +1,139 @@ +package com.hiroshi.cimoc.source; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.DecryptionUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by FEILONG on 2017/12/21. + */ + +public class MH517 extends MangaParser { + + public static final int TYPE = 70; + public static final String DEFAULT_TITLE = "我要去漫画"; + + public MH517(Source source) { + init(source, null); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException { + if (page != 1) return null; + String url = StringUtils.format("http://m.517manhua.com/statics/search.aspx?key=%s", keyword); + return new Request.Builder().url(url).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("ul#listbody > li")) { + @Override + protected Comic parse(Node node) { + final String cid = node.href("a.ImgA"); + final String title = node.text("a.txtA"); + final String cover = node.attr("a.ImgA > img", "src"); + return new Comic(TYPE, cid, title, cover, "", ""); + } + }; + } + + @Override + public String getUrl(String cid) { + return "http://m.517manhua.com" + cid; + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("m.517manhua.com", ".*", 0)); + } + + @Override + public Request getInfoRequest(String cid) { + if (cid.indexOf("http://m.517manhua.com") == -1) { + cid = "http://m.517manhua.com".concat(cid); + } + return new Request.Builder().url(cid).build(); + } + + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + String title = body.attr("div#Cover > img", "title"); + String cover = body.src("div#Cover > img"); + String update = ""; + String author = ""; + String intro = body.text("p.txtDesc"); + boolean status = false; + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + for (Node node : new Node(html).list("#mh-chapter-list-ol-0 > li")) { + String title = node.text("a > span"); + String path = node.hrefWithSplit("a", 2); + list.add(new Chapter(title, path)); + } + return list; + } + + + @Override + public Request getImagesRequest(String cid, String path) { + path = StringUtils.format("http://m.517manhua.com%s/%s.html", cid, path); + return new Request.Builder().url(path).build(); + } + + @Override + public List parseImages(String html) { + List list = new ArrayList<>(); + Matcher pageMatcher = Pattern.compile("qTcms_S_m_murl_e=\"(.*?)\"").matcher(html); + final String mangaid = StringUtils.match("var qTcms_S_m_id=\"(\\w+?)\";", html, 1); + if (!pageMatcher.find()) return null; + try { + final String imgArrStr = DecryptionUtils.base64Decrypt(pageMatcher.group(1)); + int i = 0; + for (String item : imgArrStr.split("\\$.*?\\$")) { + final String url = "http://m.517manhua.com/statics/pic/?p=" + item + "&wapif=1&picid=" + mangaid + "&m_httpurl="; + list.add(new ImageUrl(i++, url, false)); + } + } finally { + return list; + } + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://m.517manhua.com/"); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/MH57.java b/app/src/main/java/com/hiroshi/cimoc/source/MH57.java new file mode 100644 index 00000000..c9ce119a --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/MH57.java @@ -0,0 +1,321 @@ +package com.hiroshi.cimoc.source; + +import android.util.Pair; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaCategory; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.DecryptionUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.json.JSONArray; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by Hiroshi on 2016/10/3. + */ + +public class MH57 extends MangaParser { + + public static final int TYPE = 8; + public static final String DEFAULT_TITLE = "57漫画"; + + private static final String[] servers = { + "http://images.lancaier.com" + }; + + public MH57(Source source) { + init(source, new Category()); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) { + String url = StringUtils.format("http://m.wuqimh.com/search/q_%s-p-%d", keyword, page); + return new Request.Builder().url(url).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + for (Node node : body.list("div.book-result > div.pager-cont > span.pager > span.current")) { + try { + if (Integer.parseInt(node.text()) < page) { + return null; + } + } catch (NumberFormatException e) { + e.printStackTrace(); + } + } + return new NodeIterator(body.list("#data_list > li")) { + @Override + protected Comic parse(Node node) { + String cid = node.hrefWithSplit("a:eq(0)", 0); + String title = node.text("a:eq(0) > h3"); + String cover = node.attr("a:eq(0) > div.thumb > img", "data-src"); + String update = node.text("dl:eq(4) > dd"); + String author = node.text("dl:eq(1) > a > dd"); + return new Comic(TYPE, cid, title, cover, update, author); + } + }; + } + + @Override + public String getUrl(String cid) { + return "http://m.wuqimh.com/".concat(cid); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("m.wuqimh.com")); + } + + @Override + public Request getInfoRequest(String cid) { + String url = "http://m.wuqimh.com/".concat(cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String title = body.text("div.main-bar > h1"); + String cover = body.src("div.book-detail > div.cont-list > div.thumb > img"); + String update = body.text("div.book-detail > div.cont-list > dl:eq(7) > dd"); + String author = body.text("div.book-detail > div.cont-list > dl:eq(3) > dd"); + String intro = body.text("#bookIntro"); + boolean status = isFinish(body.text("div.book-detail > div.cont-list > div.thumb > i")); + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list("#chapterList > ul > li > a")) { + String title = node.text(); + String path = node.hrefWithSplit(1); + list.add(new Chapter(title, path)); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("http://m.wuqimh.com/%s/%s.html", cid, path); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + String packed = StringUtils.match("eval(.*?)\\n", html, 1); + if (packed != null) { + String result = DecryptionUtils.evalDecrypt(packed); + String jsonString = StringUtils.match("'fs':\\s*(\\[.*?\\])", result, 1); + try { + JSONArray array = new JSONArray(jsonString); + int size = array.length(); + for (int i = 0; i != size; ++i) { + String url = array.getString(i); + if(url.indexOf("http://") == -1){ + url = servers[0] + url; + } + list.add(new ImageUrl(i + 1, url, false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).text("div.book-detail > div.cont-list > dl:eq(7) > dd"); + } + + @Override + public List parseCategory(String html, int page) { + List list = new ArrayList<>(); + Node body = new Node(html); + for (Node node : body.list("span.pager > span.current")) { + try { + if (Integer.parseInt(node.text()) < page) { + return list; + } + } catch (NumberFormatException e) { + e.printStackTrace(); + } + } + for (Node node : body.list("#contList > li")) { + String cid = node.hrefWithSplit("a", 0); + String title = node.attr("a", "title"); + String cover = node.attr("a > img", "data-src"); + String update = node.textWithSubstring("span.updateon", 4, 14); + list.add(new Comic(TYPE, cid, title, cover, update, null)); + } + return list; + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://m.wuqimh.com/"); + } + + private static class Category extends MangaCategory { + + @Override + public boolean isComposite() { + return true; + } + + @Override + public String getFormat(String... args) { + return StringUtils.format("http://www.5qmh.com/list/area-%s-smid-%s-year-%s-lz-%s-order-%s-p-%%d", + args[CATEGORY_AREA], args[CATEGORY_SUBJECT], args[CATEGORY_YEAR], args[CATEGORY_PROGRESS], args[CATEGORY_ORDER]); + } + + @Override + public List> getSubject() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("热血", "1")); + list.add(Pair.create("武侠", "2")); + list.add(Pair.create("搞笑", "3")); + list.add(Pair.create("耽美", "4")); + list.add(Pair.create("爱情", "5")); + list.add(Pair.create("科幻", "6")); + list.add(Pair.create("魔法", "7")); + list.add(Pair.create("神魔", "8")); + list.add(Pair.create("竞技", "9")); + list.add(Pair.create("格斗", "10")); + list.add(Pair.create("机战", "11")); + list.add(Pair.create("体育", "12")); + list.add(Pair.create("运动", "13")); + list.add(Pair.create("校园", "14")); + list.add(Pair.create("励志", "15")); + list.add(Pair.create("历史", "16")); + list.add(Pair.create("伪娘", "17")); + list.add(Pair.create("百合", "18")); + list.add(Pair.create("后宫", "19")); + list.add(Pair.create("治愈", "20")); + list.add(Pair.create("美食", "21")); + list.add(Pair.create("推理", "22")); + list.add(Pair.create("悬疑", "23")); + list.add(Pair.create("恐怖", "24")); + list.add(Pair.create("职场", "25")); + list.add(Pair.create("BL", "26")); + list.add(Pair.create("剧情", "27")); + list.add(Pair.create("生活", "28")); + list.add(Pair.create("幻想", "29")); + list.add(Pair.create("战争", "30")); + list.add(Pair.create("仙侠", "33")); + list.add(Pair.create("性转换", "40")); + list.add(Pair.create("冒险", "41")); + list.add(Pair.create("其他", "32")); + return list; + } + + @Override + public boolean hasArea() { + return true; + } + + @Override + public List> getArea() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("日本", "日本")); + list.add(Pair.create("港台", "港台")); + list.add(Pair.create("欧美", "欧美")); + list.add(Pair.create("韩国", "韩国")); + list.add(Pair.create("国产", "国产")); + list.add(Pair.create("其它", "其它")); + return list; + } + + @Override + public boolean hasYear() { + return true; + } + + @Override + public List> getYear() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("2017", "2017")); + list.add(Pair.create("2016", "2016")); + list.add(Pair.create("2015", "2015")); + list.add(Pair.create("2014", "2014")); + list.add(Pair.create("2013", "2013")); + list.add(Pair.create("2012", "2012")); + list.add(Pair.create("2011", "2011")); + list.add(Pair.create("2010", "2010")); + list.add(Pair.create("2009", "2009")); + list.add(Pair.create("2008", "2008")); + list.add(Pair.create("2007", "2007")); + list.add(Pair.create("2006", "2006")); + list.add(Pair.create("2005", "2005")); + list.add(Pair.create("2004", "2004")); + list.add(Pair.create("2003", "2003")); + list.add(Pair.create("2002", "2002")); + list.add(Pair.create("2001", "2001")); + list.add(Pair.create("2000", "2000")); + list.add(Pair.create("1990", "1990")); + return list; + } + + @Override + public boolean hasProgress() { + return true; + } + + @Override + public List> getProgress() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部", "")); + list.add(Pair.create("连载", "1")); + list.add(Pair.create("完结", "2")); + return list; + } + + @Override + protected boolean hasOrder() { + return true; + } + + @Override + protected List> getOrder() { + List> list = new ArrayList<>(); + list.add(Pair.create("更新", "addtime")); + list.add(Pair.create("发布", "id")); + list.add(Pair.create("人气", "hits")); + list.add(Pair.create("评分", "gold")); + return list; + } + + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/MHLove.java b/app/src/main/java/com/hiroshi/cimoc/source/MHLove.java new file mode 100644 index 00000000..90bd098e --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/MHLove.java @@ -0,0 +1,147 @@ +package com.hiroshi.cimoc.source; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.DecryptionUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.LinkedList; +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by ZhiWen on 2019/02/25. + */ + +public class MHLove extends MangaParser { + + public static final int TYPE = 27; + public static final String DEFAULT_TITLE = "漫画Love"; + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, false); + } + + public MHLove(Source source) { + init(source, null); + } + + @Override + public Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException { + if (page != 1) { + return null; + } + String url = StringUtils.format("http://m.manhualove.com/statics/search.aspx?key=%s", keyword); + return new Request.Builder() + .addHeader("Referer", "http://m.manhualove.com/") + .url(url).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("#listbody > li")) { + @Override + protected Comic parse(Node node) { + + String cover = node.attr("a:eq(0) > img", "src"); + + String title = node.text("a:eq(1)"); + String cid = node.attr("a:eq(0)", "href"); + cid = cid.substring(1, cid.length() - 1); + + return new Comic(TYPE, cid, title, cover, null, null); + } + }; + } + + @Override + public Request getInfoRequest(String cid) { + String url = "http://m.manhualove.com/".concat(cid) + "/"; + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + String cover = body.src("#Cover > img"); + String intro = body.text("div.Introduct > p:eq(1)"); + String title = body.attr("#Cover > img", "title"); + + String update = body.text("div.sub_r > p:eq(3) > span.date"); + String author = body.text("div.sub_r > p:eq(1)"); + + // 连载状态 + boolean status = isFinish(body.text("div.sub_r > p:eq(0)")); + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + for (Node node : new Node(html).list("#mh-chapter-list-ol-0 > li > a")) { + String title = node.text(); + String path = node.hrefWithSplit(2); + list.add(new Chapter(title, path)); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("http://m.manhualove.com/%s/%s.html", cid, path); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + String str = StringUtils.match("qTcms_S_m_murl_e=\"(.*?)\"", html, 1); + if (str != null) { + try { + str = DecryptionUtils.base64Decrypt(str); + String[] array = str.split("\\$qingtiandy\\$"); + String preUrl = ""; + // 当解密出来的URL地址不包含 "https://res.mhkan.com/images/comic" 时,需要加上下面的前缀,否则不需要 + if(!array[0].contains("http")){ + preUrl = "http://www.dc619.com"; + } + for (int i = 0; i != array.length; ++i) { +// http://www.dc619.com/upload2/20067/2019/03-20/20190320133920_3803el39A.D.cjou_small.jpg +// https://res.mhkan.com/images/comic/449/897246/1551916012_ETEbxAU1-ov3pF4.jpg + list.add(new ImageUrl(i + 1, preUrl + array[i], false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + // 这里表示的是更新时间 + return new Node(html).text("div.sub_r > p:eq(3) > span.date"); + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://m.manhualove.com/"); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/ManHuaDB.java b/app/src/main/java/com/hiroshi/cimoc/source/ManHuaDB.java new file mode 100644 index 00000000..40bc7323 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/ManHuaDB.java @@ -0,0 +1,178 @@ +package com.hiroshi.cimoc.source; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.google.gson.JsonObject; +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.DecryptionUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by ZhiWen on 2019/02/25. + */ + +public class ManHuaDB extends MangaParser { + + public static final int TYPE = 46; + public static final String DEFAULT_TITLE = "漫画DB"; + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + public ManHuaDB(Source source) { + init(source, null); + } + + @Override + public Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException { + String url = ""; + if (page == 1) { + url = StringUtils.format("https://www.manhuadb.com/search?q=%s", keyword); + } + return new Request.Builder().url(url).build(); + } + + @Override + public String getUrl(String cid) { + return "https://www.manhuadb.com/manhua/".concat(cid); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("www.manhuadb.com")); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("a.d-block")) { + @Override + protected Comic parse(Node node) { + String cid = node.hrefWithSplit(1); + String title = node.attr("title"); + String cover = node.attr("img", "data-original"); + return new Comic(TYPE, cid, title, cover, null, null); + } + }; + } + + @Override + public Request getInfoRequest(String cid) { + String url = "https://www.manhuadb.com/manhua/".concat(cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + String title = body.text("h1.comic-title"); +// String cover = body.src("div.cover > img"); // 这一个封面可能没有 + String cover = body.src("td.comic-cover > img"); + String author = body.text("a.comic-creator"); + String intro = body.text("p.comic_story"); + boolean status = isFinish(body.text("a.comic-pub-state")); + + String update = body.text("a.comic-pub-end-date"); + if (update == null || update.equals("")) { + update = body.text("a.comic-pub-date"); + } + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + for (Node node : new Node(html).list("#comic-book-list > div > ol > li > a")) { + String title = node.attr("title"); + String path = node.hrefWithSplit(2); + list.add(0, new Chapter(title, path)); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("https://www.manhuadb.com/manhua/%s/%s.html", cid, path); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new ArrayList<>(); + + try { + final String imageHost = StringUtils.match("data-host=\"(.*?)\"", html, 1); + final String imagePre = StringUtils.match("data-img_pre=\"(.*?)\"", html, 1); + final String base64Data = StringUtils.match("var img_data = '(.*?)';", html, 1); + final String jsonStr = DecryptionUtils.base64Decrypt(base64Data); + final JSONArray imageList = JSON.parseArray(jsonStr); + + for(int i = 0; i < imageList.size(); i++ ) { + final JSONObject image = imageList.getJSONObject(i); + + final String imageUrl = imageHost + imagePre + image.getString("img"); + + list.add(new ImageUrl(image.getIntValue("p"), imageUrl, false)); + } + + } catch (Exception e) { + e.printStackTrace(); + } + + return list; + } + + @Override + public Request getLazyRequest(String url) { + return null; + } + + @Override + public String parseLazy(String html, String url) { + return null; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + // 这里表示的是更新时间 + Node body = new Node(html); + String update = body.text("a.comic-pub-end-date"); + if (update == null || update.equals("")) { + update = body.text("a.comic-pub-date"); + } + return update; + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "https://www.manhuadb.com"); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/MangaBZ.kt b/app/src/main/java/com/hiroshi/cimoc/source/MangaBZ.kt new file mode 100644 index 00000000..4cca37f8 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/MangaBZ.kt @@ -0,0 +1,162 @@ +package com.hiroshi.cimoc.source + +import android.os.Build +import com.hiroshi.cimoc.model.Chapter +import com.hiroshi.cimoc.model.Comic +import com.hiroshi.cimoc.model.ImageUrl +import com.hiroshi.cimoc.model.Source +import com.hiroshi.cimoc.parser.MangaParser +import com.hiroshi.cimoc.parser.NodeIterator +import com.hiroshi.cimoc.parser.SearchIterator +import com.hiroshi.cimoc.parser.UrlFilter +import com.hiroshi.cimoc.soup.Node +import com.hiroshi.cimoc.utils.DecryptionUtils +import okhttp3.Headers +import okhttp3.Request +import java.io.UnsupportedEncodingException +import java.text.SimpleDateFormat +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.* + +/** + * Created by FEILONG on 2017/12/21. + * need fix + */ +class MangaBZ(source: Source?) : MangaParser() { + @Throws(UnsupportedEncodingException::class) + override fun getSearchRequest(keyword: String, page: Int): Request { + var url = "http://www.mangabz.com/search?title=$keyword&page=$page" + return Request.Builder().url(url).build() + } + + override fun getSearchIterator(html: String, page: Int): SearchIterator { + val body = Node(html) + return object : NodeIterator(body.list(".mh-item")) { + override fun parse(node: Node): Comic { + var cid = node.attr("a", "href").trim('/') + val title = node.text(".title") + val cover = node.attr(".mh-cover", "src") + val update = node.text(".chapter > a") + val author = "" + return Comic(TYPE, cid, title, cover, update, author) + } + } + } + + override fun getUrl(cid: String): String { + return "http://www.mangabz.com/$cid/" + } + + override fun initUrlFilterList() { + filter.add(UrlFilter("www.mangabz.com")) + } + + override fun getInfoRequest(cid: String): Request { + val url = "http://www.mangabz.com/$cid/" + return Request.Builder().url(url).build() + } + + @Throws(UnsupportedEncodingException::class) + override fun parseInfo(html: String, comic: Comic) { + val body = Node(html) + val title = body.text(".detail-info-title") + val cover = body.src(".detail-info-cover") + val update = body.text(".detail-list-form-title") + val author = body.text(".detail-info-tip") + val intro = body.text(".detail-info-content") + val status = isFinish(".detail-list-form-title") + comic.setInfo(title, cover, update, intro, author, status) + } + + override fun parseChapter(html: String): List { + val list: MutableList = LinkedList() + for (node in Node(html).list("#chapterlistload > a")) { + var title = node.attr("title") + if (title == "") title = node.text() + val path = node.href().trim('/') + list.add(Chapter(title, path)) + } + return list + } + + var _cid = "" + var _path = "" + + override fun getImagesRequest(cid: String, path: String): Request { + val url = "http://www.mangabz.com/$path/" + this._cid = cid + this._path = path + return Request.Builder() + .url(url) + .build() + } + + fun getValFromRegex(html: String, keyword: String, searchfor: String): String? { + val re = Regex("""var\s+""" + keyword + """\s*=\s*""" + searchfor + """\s*;""") + val match = re.find(html) + return match?.groups?.get(1)?.value + } + + override fun parseImages(html: String): List { + val list: MutableList = LinkedList() + try { + // get page num + val mid = getValFromRegex(html, "MANGABZ_MID", "(\\w+)")!! + val cid = getValFromRegex(html, "MANGABZ_CID", "(\\w+)")!! + val sign = getValFromRegex(html, "MANGABZ_VIEWSIGN", """\"(\w+)\"""")!! + val pageCount = getValFromRegex(html, "MANGABZ_IMAGE_COUNT", "(\\d+)")!!.toInt() + for (i in 1..pageCount) { + val url = "http://www.mangabz.com/$_path/chapterimage.ashx?cid=$cid&page=$i&key=&_cid=$cid&_mid=$mid&_sign=$sign&_dt=" + list.add(ImageUrl(i + 1, url, true)) + } + } catch (e: Exception) { + e.printStackTrace() + } + + return list + } + + override fun getLazyRequest(url: String?): Request? { + val dateFmt = "yyyy-MM-dd+HH:mm:ss" + val dateStr = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val current = LocalDateTime.now() + val formatter = DateTimeFormatter.ofPattern(dateFmt) + current.format(formatter) + } else { + var date = Date(); + val formatter = SimpleDateFormat(dateFmt) + formatter.format(date) + } + + val url = url + dateStr + return Request.Builder() + .addHeader("Referer", "http://www.mangabz.com/$_path/") + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36") + .url(url).build() + } + + override fun parseLazy(html: String?, url: String?): String? { + val image = DecryptionUtils.evalDecrypt(html).split(',').get(0) + return image + } + + override fun getHeader(): Headers { + return Headers.of("Referer", "http://www.mangabz.com/") + } + + companion object { + @JvmStatic + fun getDefaultSource(): Source { + return Source(null, DEFAULT_TITLE, TYPE, true); + } + + const val TYPE = 82 + const val DEFAULT_TITLE = "MangaBZ" + } + + init { + init(source, null) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hiroshi/cimoc/source/MangaNel.java b/app/src/main/java/com/hiroshi/cimoc/source/MangaNel.java new file mode 100644 index 00000000..12cd0c84 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/MangaNel.java @@ -0,0 +1,311 @@ +package com.hiroshi.cimoc.source; + +import android.util.Pair; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.JsonIterator; +import com.hiroshi.cimoc.parser.MangaCategory; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.Request; +import okhttp3.RequestBody; + +/** + * Created by nich on 2017/8/6. + */ + +public class MangaNel extends MangaParser { + + public static final int TYPE = 43; + public static final String DEFAULT_TITLE = "MangaNel"; + + public MangaNel(Source source) { + init(source, new Category()); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + /** + * 搜索页的 HTTP 请求 + * + * @param keyword 关键字 + * @param page 页码 + */ + @Override + public Request getSearchRequest(String keyword, int page) { + String url = "http://manganelo.com/home/getjson_searchstory"; + + RequestBody formBody = new FormBody.Builder() + .add("searchword", keyword) + .add("search_style", "tentruyen") + .build(); + + return new Request.Builder().url(url).post(formBody).build(); + } + + /** + * 获取搜索结果迭代器,这里不直接解析成列表是为了多图源搜索时,不同图源漫画穿插的效果 + * + * @param html 页面源代码 + * @param page 页码,可能对于一些判断有用 + */ + @Override + public SearchIterator getSearchIterator(String html, int page) { + if (page > 1) return null; // MangaNel的搜索不支持分页,最多仅返回20个搜索结果 + + try { + return new JsonIterator(new JSONArray(html)) { + @Override + protected Comic parse(JSONObject object) { + try { + String cid = object.getString("nameunsigned"); + String title = object.getString("name"); + if (title.contains(" ul.manga-info-text > li > h1"); + String cover = body.src("div.manga-info-pic > img"); + String update = body.list("div.manga-info-top > ul.manga-info-text > li") + .get(3) + .text() + .replace("Last updated : ", ""); + String author = body.list("div.manga-info-top > ul.manga-info-text > li").get(1).text("a"); + String intro = body.text("#noidungm"); + boolean status = isFinish(body.list("div.manga-info-top > ul.manga-info-text > li") + .get(2) + .text()); + comic.setInfo(title, cover, update, intro, author, status); + } + + /** + * 解析章节列表 + * + * @param html 页面源代码 + */ + @Override + public List parseChapter(String html) { + Set set = new LinkedHashSet<>(); + Node body = new Node(html); + for (Node node : body.list("div.chapter-list > div.row")) { + String title = node.text("span > a"); + String path = node.list("span > a").get(0).href(); + set.add(new Chapter(title, path)); + } + return new LinkedList<>(set); + } + + /** + * 图片列表的 HTTP 请求 + * + * @param cid 漫画 ID + * @param path 章节路径 + */ + @Override + public Request getImagesRequest(String cid, String path) { + return new Request.Builder().url(path).build(); + } + + /** + * 解析图片列表,若为惰性加载,则 {@link ImageUrl#lazy} 为 true + * 惰性加载的情况,一次性不能拿到所有图片链接,例如网站使用了多次异步请求 {@link DM5#parseImages},或需要跳转到不同页面 + * 才能获取 {@link HHSSEE#parseImages},这些情况一般可以根据页码构造出相应的请求链接,到阅读时再解析 + * 支持多个链接 ,例如 {@link IKanman#parseImages} + * + * @param html 页面源代码 + */ + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + Node body = new Node(html); + int i = 0; + for (Node node : body.list("div.vung-doc > img")) { + list.add(new ImageUrl(++i, node.src(), false)); + } + return list; + } + + /** + * 获取下载图片时的 HTTP 请求头,一般用来设置 Referer 和 Cookie + */ + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://manganelo.com/"); + } + + @Override + public Request getCategoryRequest(String format, int page) { + return super.getCategoryRequest(format, page); + } + + @Override + public List parseCategory(String html, int page) { + List list = new LinkedList<>(); + Node body = new Node(html); + int total = Integer.parseInt(StringUtils.match("\\d+", body.list("div.phan-trang > a") + .get(4) + .href(), 0)); + if (page <= total) { + for (Node node : body.list("div.truyen-list > div.list-truyen-item-wrap")) { + String cid = node.href("h3 > a").replace("http://manganelo.com/manga/", ""); + String title = node.text("h3 > a"); + String cover = node.src("a > img"); + String update = node.list("a").get(2).text(); + String author = node.text("div:eq(1) > div:eq(1) > dl:eq(1) > dd > a"); + list.add(new Comic(TYPE, cid, title, cover, update, author)); + } + } + return list; + } + + private static class Category extends MangaCategory { + + @Override + public boolean isComposite() { + return true; + } + + @Override + public String getFormat(String... args) { + String path = "category=".concat(args[CATEGORY_SUBJECT]) + .concat("&alpha=all&state=") + .concat(args[CATEGORY_PROGRESS]) + .concat("&group=all") + .trim(); + path = path.replaceAll("\\s+", "-"); + return StringUtils.format("http://manganel.com/manga_list?type=new&page=%%d&%s", path); + } + + @Override + protected List> getSubject() { + List> list = new ArrayList<>(); + list.add(Pair.create("All", "all")); + list.add(Pair.create("Action", "2")); +// list.add(Pair.create("Adult", "3")); + list.add(Pair.create("Adventure", "4")); + list.add(Pair.create("Comedy", "6")); + list.add(Pair.create("Cooking", "7")); +// list.add(Pair.create("Donjinshi", "9")); + list.add(Pair.create("Drama", "10")); +// list.add(Pair.create("Ecchi", "11")); + list.add(Pair.create("Fantasy", "12")); +// list.add(Pair.create("Gender bender", "13")); +// list.add(Pair.create("Harem", "14")); + list.add(Pair.create("Historical", "15")); + list.add(Pair.create("Horror", "16")); + list.add(Pair.create("Josei", "17")); + list.add(Pair.create("Manhua", "44")); + list.add(Pair.create("Manhwa", "43")); + list.add(Pair.create("Martial Arts", "19")); + list.add(Pair.create("Mature", "20")); + list.add(Pair.create("Mecha", "21")); + list.add(Pair.create("Medical", "22")); + list.add(Pair.create("Mystery", "24")); + list.add(Pair.create("One Shot", "25")); + list.add(Pair.create("Psychological", "26")); + list.add(Pair.create("Romance", "27")); + list.add(Pair.create("Sci Fi", "29")); + return list; + } + + @Override + protected boolean hasArea() { + return false; + } + + @Override + protected List> getArea() { + List> list = new ArrayList<>(); + list.add(Pair.create("All", "")); + return list; + } + + @Override + public boolean hasProgress() { + return true; + } + + @Override + public List> getProgress() { + List> list = new ArrayList<>(); + list.add(Pair.create("All", "all")); + list.add(Pair.create("Ongoing", "Ongoing")); + list.add(Pair.create("Completed", "Completed")); + return list; + } + + @Override + protected boolean hasOrder() { + return false; + } + + @Override + protected List> getOrder() { + List> list = new ArrayList<>(); + list.add(Pair.create("All", "")); + return list; + } + + } +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/Manhuatai.java b/app/src/main/java/com/hiroshi/cimoc/source/Manhuatai.java new file mode 100755 index 00000000..feec3a1f --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/Manhuatai.java @@ -0,0 +1,321 @@ +package com.hiroshi.cimoc.source; + + + +import android.util.Pair; + +import com.alibaba.fastjson.JSON; +import com.google.common.collect.Lists; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.annotations.SerializedName; +import com.hiroshi.cimoc.App; +import com.hiroshi.cimoc.core.Manga; +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.JsonIterator; +import com.hiroshi.cimoc.parser.MangaCategory; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.soup.MDocument; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.ui.activity.ResultActivity; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import okhttp3.Headers; +import okhttp3.Request; + + +/** + * Created by reborn on 18-1-18. + */ + +public class Manhuatai extends MangaParser { + + public static final int TYPE = 49; + public static final String DEFAULT_TITLE = "漫画台"; + public static final String baseUrl = "https://m.manhuatai.com"; + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + public Manhuatai(Source source) { + init(source, new Category()); + } + + @Override + public Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException { + String url = StringUtils.format(baseUrl + "/api/getsortlist/?product_id=2&productname=mht&platformname=wap&orderby=click&search_key=%s&page=%d&size=48", + URLEncoder.encode(keyword, "UTF-8"), page); + + return new Request.Builder().url(url).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) throws JSONException { + JSONObject object = new JSONObject(html); + + return new JsonIterator(object.getJSONObject("data").getJSONArray("data")) { + @Override + protected Comic parse(JSONObject object) throws JSONException { + String title = object.getString("comic_name"); + String cid = object.getString("comic_newid"); + String cover = "https://image.yqmh.com/mh/" + object.getString("comic_id") + ".jpg-300x400.webp"; + String author = null; + String update = null; + return new Comic(TYPE, cid, title, cover, update, author); + } + }; + } + + private Node getComicNode(String cid) throws Manga.NetworkErrorException { + Request request = getInfoRequest(cid); + String html = Manga.getResponseBody(App.getHttpClient(), request); + return new Node(html); + } + +// private String getResponseBody(OkHttpClient client, Request request) throws Manga.NetworkErrorException { +// Response response = null; +// try { +// response = client.newCall(request).execute(); +// if (response.isSuccessful()) { +//// return response.body().string(); +// +// // 1.修正gb2312编码网页读取错误 +// byte[] bodybytes = response.body().bytes(); +// String body = new String(bodybytes); +// if (body.indexOf("charset=gb2312") != -1) { +// body = new String(bodybytes, "GB2312"); +// } +// return body; +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } finally { +// if (response != null) { +// response.close(); +// } +// } +// throw new Manga.NetworkErrorException(); +// } + + @Override + public Request getInfoRequest(String cid) { + String url = "https://www.manhuatai.com/".concat(cid) + "/"; + return new Request.Builder().url(url).build(); + } + + //获取封面等信息(非搜索页) + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + String title = body.attr("h1#detail-title", "title"); +// String cover = body.src("#offlinebtn-container > img");//封面链接已改到style属性里了 + String cover = body.attr("div.detail-cover > img", "data-src"); + cover = "https:" + cover; +// Log.i("Cover", cover); + String update = body.text("span.update").substring(0,10); + String author = null; + String intro = body.text("div#js_comciDesc > p.desc-content"); +// boolean status = isFinish(body.text("div.jshtml > ul > li:nth-child(2)").substring(3)); + comic.setInfo(title, cover, update, intro, author, false); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + for (Node node : new Node(html).list("ol#j_chapter_list > li > a")) { + String title = node.attr( "title"); +// String path = node.hrefWithSplit(0);//于2018.3失效 + String path = node.hrefWithSplit(1); +// Log.i("Path", path); + list.add(new Chapter(title, path)); + } + return Lists.reverse(list); + } + + private String _path = null; + + //获取漫画图片Request + @Override + public Request getImagesRequest(String cid, String path) { + _path = path; + String url = StringUtils.format("https://m.manhuatai.com/api/getcomicinfo_body?product_id=2&productname=mht&platformname=wap&comic_newid=%s", cid); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + try { + JSONObject object = new JSONObject(html); + if (object.getInt("status") != 0) { + return list; + } + + JSONArray chapters = object.getJSONObject("data").getJSONArray("comic_chapter"); + JSONObject chapter = null; + for (int i = 0; i < chapters.length(); i++) { + chapter = chapters.getJSONObject(i); + String a = chapter.getString("chapter_id"); + if(a.equals(_path)) { + break; + } + } + + String ImagePattern = "http://mhpic." + chapter.getString("chapter_domain") + chapter.getString("rule") + "-mht.low.webp"; + + for (int index = chapter.getInt("start_num"); index <= chapter.getInt("end_num"); index++) { + String image = ImagePattern.replaceFirst("\\$\\$", Integer.toString(index)); + list.add(new ImageUrl(index, image, false)); + } + } catch (JSONException ex) { + // ignore + } + + return list; + } + + + class MhInfo { + @SerializedName("startimg") + int startimg; + @SerializedName("totalimg") + int totalimg; + @SerializedName("pageid") + int pageid; + @SerializedName("comic_size") + String comic_size; + @SerializedName("domain") + String domain; + @SerializedName("imgpath") + String imgpath; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).text("div.jshtml > ul > li:nth-child(5)").substring(3); + } + + @Override + public List parseCategory(String html, int page) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list("a.sdiv")) { + String cid = node.hrefWithSplit(0); + String title = node.attr("title"); + String cover = node.getChild("img").attr("data-url"); +// String cover1 = node.attr("div > img", "data-url"); + Node node1 = null; + try { + node1 = getComicNode(cid); + } catch (Manga.NetworkErrorException e) { + e.printStackTrace(); + } + if (StringUtils.isEmpty(cover) && node1 != null) { +// cover = node.src("div > img"); + cover = node1.src("#offlinebtn-container > img"); + } +// String update = node.text("div > span:nth-child(1)"); +// String author = "佚名"; +// String cover = null; + String author = null; + String update = null; + if (node1 != null) { +// cover = getComicNode(cid).src("#offlinebtn-container > img"); + author = node1.text("div.jshtml > ul > li:nth-child(3)").substring(3); + update = node1.text("div.jshtml > ul > li:nth-child(5)").substring(3); + } + list.add(new Comic(TYPE, cid, title, cover, update, author)); + } + return list; + } + + private static class Category extends MangaCategory { + + @Override + public boolean isComposite() { + return true; + } + + @Override + public String getFormat(String... args) { + return StringUtils.format("https://www.manhuatai.com/%s_p%%d.html", + args[CATEGORY_SUBJECT]); + } + + @Override + public List> getSubject() { + List> list = new ArrayList<>(); + list.add(Pair.create("全部漫画", "all")); + list.add(Pair.create("知音漫客", "zhiyinmanke")); + list.add(Pair.create("神漫", "shenman")); + list.add(Pair.create("风炫漫画", "fengxuanmanhua")); + list.add(Pair.create("漫画周刊", "manhuazhoukan")); + list.add(Pair.create("飒漫乐画", "samanlehua")); + list.add(Pair.create("飒漫画", "samanhua")); + list.add(Pair.create("漫画世界", "manhuashijie")); +// list.add(Pair.create("排行榜", "top")); + +// list.add(Pair.create("热血", "rexue")); +// list.add(Pair.create("神魔", "shenmo")); +// list.add(Pair.create("竞技", "jingji")); +// list.add(Pair.create("恋爱", "lianai")); +// list.add(Pair.create("霸总", "bazong")); +// list.add(Pair.create("玄幻", "xuanhuan")); +// list.add(Pair.create("穿越", "chuanyue")); +// list.add(Pair.create("搞笑", "gaoxiao")); +// list.add(Pair.create("冒险", "maoxian")); +// list.add(Pair.create("萝莉", "luoli")); +// list.add(Pair.create("武侠", "wuxia")); +// list.add(Pair.create("社会", "shehui")); +// list.add(Pair.create("都市", "dushi")); +// list.add(Pair.create("漫改", "mangai")); +// list.add(Pair.create("杂志", "zazhi")); +// list.add(Pair.create("悬疑", "xuanyi")); +// list.add(Pair.create("恐怖", "kongbu")); +// list.add(Pair.create("生活", "shenghuo")); + return list; + } + + @Override + protected boolean hasOrder() { + return false; + } + + @Override + protected List> getOrder() { +// List> list = new ArrayList<>(); +// list.add(Pair.create("更新", "update")); +// list.add(Pair.create("发布", "index")); +// list.add(Pair.create("人气", "view")); + return null; + } + + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "https://m.manhuatai.com"); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/MiGu.java b/app/src/main/java/com/hiroshi/cimoc/source/MiGu.java new file mode 100644 index 00000000..8af4d778 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/MiGu.java @@ -0,0 +1,195 @@ +package com.hiroshi.cimoc.source; + +import android.util.Base64; + +import com.google.common.collect.Lists; +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.RegexIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import okhttp3.Call; +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** + * Created by FEILONG on 2017/12/21. + */ + +public class MiGu extends MangaParser { + + public static final int TYPE = 58; + public static final String DEFAULT_TITLE = "咪咕漫画"; + private String _cid, _path; + + public MiGu(Source source) { + init(source, null); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException { + //这编码牛逼,不认识... + //天 => JUU1JUE0JUE5 + //好吧,搞出来了 + //天 ===utf-8===> \xE5\xA4\xA9 ===\x->%=====> %E5%A1%A9 ===base64=====> JUU1JUE0JUE5 + byte[] keywordByte = keyword.getBytes(Charset.forName("UTF-8")); + String keyPre = ""; + for (byte k : keywordByte) { + keyPre += "%" + String.format("%02x", k); + } + String keywordconv = Base64.encodeToString(keyPre.toUpperCase().getBytes(), keyPre.length()).trim(); + String url = StringUtils.format("http://www.migudm.cn/search/result/list.html?hintKey=%s&hintType=2&pageSize=30&pageNo=%d", + keywordconv, + page); + return new Request.Builder().url(url).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Matcher m = Pattern.compile("href=\"\\/comic\\/(.*?).html\" title=\"(.*?)\">\\s+").matcher(html); + return new RegexIterator(m) { + @Override + protected Comic parse(Matcher match) { + return new Comic(TYPE, match.group(1), match.group(2), match.group(3), "", ""); + } + }; + } + + @Override + public String getUrl(String cid) { + return StringUtils.format("http://www.migudm.cn/comic/%s.html", cid); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("www.migudm.cn")); + filter.add(new UrlFilter("m.migudm.cn")); + } + + @Override + public Request getInfoRequest(String cid) { + return new Request.Builder().url(StringUtils.format("http://www.migudm.cn/comic/%s.html", cid)).build(); + } + + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + String title = body.text("div.inner > .ctdbRight > .ctdbRightInner > .title").trim(); + String cover = body.attr("div.inner > .ctdbLeft > a > img", "src"); +// String update = body.text("span.date").trim(); +// String author = body.text("p.author").trim(); + String intro = body.text("#worksDesc").trim(); + boolean status = false; + comic.setInfo(title, cover, "", intro, "", status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + Matcher m = Pattern.compile("").matcher(html); + while (m.find()) { + list.add(new Chapter(m.group(2), m.group(1))); + } + return Lists.reverse(list); + } + + public String httpGet(String url) { + OkHttpClient okHttpClient = new OkHttpClient.Builder().readTimeout(10, TimeUnit.SECONDS).build(); + final Request request = new Request.Builder() + .url(url) + .addHeader("user-agent", "Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30") + .get()//默认就是GET请求,可以不写 + .build(); + Call call = okHttpClient.newCall(request); + try { + Response response = call.execute(); + return response.body().string(); + } catch (Exception ex) { + return ""; + } + } + + @Override + public Request getImagesRequest(String cid, String path) { + _cid = cid; + _path = path; + String url = StringUtils.format("http://www.migudm.cn/%s/chapter/%s.html", cid, path); + String html = httpGet(url); + Matcher m = Pattern.compile("").matcher(html); + if (m.find()) { + url = "http://www.migudm.cn/opus/webQueryWatchOpusInfo.html?".concat(m.group(1)); + return new Request.Builder().url(url).build(); + } + return null; +// String url = StringUtils.format("http://m.migudm.cn/comic/readImage.html?opusType=2&hwOpusId=090000000865&hwItemId=091000017043"); + } + + @Override + public List parseImages(String html) { + List list = new ArrayList<>(); + try { + JSONObject json = new JSONObject(html); + JSONArray jpgJsonArr = json.getJSONObject("data").getJSONArray("jpgList"); + for (int i = 0; i < jpgJsonArr.length(); i++) { + JSONObject j = jpgJsonArr.getJSONObject(i); + list.add(new ImageUrl(i + 1, j.getString("url"), false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + return list; + } + +// @Override +// public Request getLazyRequest(String url) { +// return new Request.Builder() +//// .addHeader("Referer", url) +// .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 7.0;) Chrome/58.0.3029.110 Mobile") +// .addHeader("Cookie", "isAdult=1") +// .url(url).build(); +// } +// +// @Override +// public String parseLazy(String html, String url) { +// Matcher m = Pattern.compile("<\\/div>).matcher(html);
+//        if (m.find()) {
+//            return m.group(1);
+//        }
+//        return null;
+//    }
+
+    @Override
+    public Request getCheckRequest(String cid) {
+        return getInfoRequest(cid);
+    }
+
+    @Override
+    public Headers getHeader(String url) {
+        return Headers.of( div.index-container > div > a")) { + @Override + protected Comic parse(Node node) { + String cid = node.hrefWithSplit(1); + String title = node.text("div.caption"); + String author = StringUtils.match("\\[(.*?)\\]", title, 1); + title = title.replaceFirst("\\[.*?\\]\\s*", ""); + String cover = "https:".concat(node.src("img")); + return new Comic(SourceManager.SOURCE_NHENTAI, cid, title, cover, null, author); + } + }; + } + + @Override + public Request getInfoRequest(String cid) { + String url = StringUtils.format("https://nhentai.net/g/%s", cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String title = body.text("#info > h1"); + String intro = body.text("#info > h2"); + String author = body.text("#tags > div > span > a[href^=/artist/]"); + String cover = "https:".concat(body.src("#cover > a > img")); + comic.setInfo(title, cover, null, intro, author, true); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + list.add(new Chapter("全一话", "")); + return list; + } + + @Override + public Request getRecentRequest(int page) { + String url = StringUtils.format("https://nhentai.net/?page=%d", page); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseRecent(String html, int page) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list("#content > div.index-container > div > a")) { + String cid = node.hrefWithSplit(1); + String title = node.text("div.caption"); + String author = StringUtils.match("\\[(.*?)\\]", title, 1); + title = title.replaceFirst("\\[.*?\\]\\s*", ""); + String cover = "https:".concat(node.src("img")); + list.add(new Comic(SourceManager.SOURCE_NHENTAI, cid, title, cover, null, author)); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + return getInfoRequest(cid); + } + + @Override + public List parseImages(String html) { + Node body = new Node(html); + List list = new LinkedList<>(); + int count = 0; + for (Node node : body.list("#thumbnail-container > div > a > img")) { + String url = "https:".concat(node.attr("data-src")); + list.add(new ImageUrl(++count, url.replace("t.jpg", ".jpg"), false)); + } + return list; + } +*/ +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/NetEase.java b/app/src/main/java/com/hiroshi/cimoc/source/NetEase.java new file mode 100644 index 00000000..9d7f8b63 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/NetEase.java @@ -0,0 +1,170 @@ +package com.hiroshi.cimoc.source; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.JsonIterator; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.LinkedList; +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +//import com.google.gson.JsonObject; + +/** + * Created by FEILONG on 2017/12/21. + */ + +public class NetEase extends MangaParser { + + public static final int TYPE = 53; + public static final String DEFAULT_TITLE = "网易漫画"; + + public NetEase(Source source) { + init(source, null); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, false); + } + + @Override + public Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException { + String url = StringUtils.format("https://h5.manhua.163.com/search/book/key/data.json?key=%s&page=%d&pageSize=10&target=3", + keyword, page); + return new Request.Builder().url(url).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + try { + JSONObject js = new JSONObject(html); + return new JsonIterator(js.getJSONArray("records")) { + @Override + protected Comic parse(JSONObject object) { + try { + String cid = object.getString("bookId"); + String title = object.getString("title"); + String cover = object.getString("cover"); + String author = object.getString("author"); + return new Comic(TYPE, cid, title, cover, null, author); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + }; + } catch (JSONException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public String getUrl(String cid) { + return "https://h5.manhua.163.com/source/".concat(cid); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("h5.manhua.163.com")); + } + + @Override + public Request getInfoRequest(String cid) { + String url = "https://h5.manhua.163.com/source/".concat(cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String update = "null"; + String title = body.text(".sr-detail__heading"); + String intro = body.attr(".share-button", "summary"); + String author = body.text(".sr-detail__author-text"); + String cover = body.attr(".share-button", "pic"); + comic.setInfo(title, cover, update, intro, author, true); + } + + @Override + public Request getChapterRequest(String html, String cid) { + String url = StringUtils.format("https://h5.manhua.163.com/book/catalog/%s.json", cid); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + try { + JSONObject js = new JSONObject(html); + JSONArray jsarr = js.getJSONObject("catalog") + .getJSONArray("sections") + .getJSONObject(0) + .getJSONArray("sections"); + + for (int i = 0; i < jsarr.length(); i++) { + String title = jsarr.getJSONObject(i).getString("title"); + String path = jsarr.getJSONObject(i).getString("sectionId"); + list.add(new Chapter(title, path)); + } + } catch (JSONException e) { + e.printStackTrace(); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + //https://h5.manhua.163.com/reader/section/4458002705630123099/4457282705880101050.json + String url = StringUtils.format("https://h5.manhua.163.com/reader/section/%s/%s.json", cid, path); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + + try { + JSONObject js = new JSONObject(html); + JSONArray jsarr = js.getJSONArray("images"); + + for (int i = 0; i < jsarr.length(); i++) { + list.add(new ImageUrl(i + 1, jsarr.getJSONObject(i).getString("highUrl"), false)); + } + } catch (JSONException e) { + e.printStackTrace(); + } + + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).text("div.book-detail > div.cont-list > dl:eq(2) > dd"); + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "https://h5.manhua.163.com"); + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/Null.java b/app/src/main/java/com/hiroshi/cimoc/source/Null.java new file mode 100644 index 00000000..bbf51d1f --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/Null.java @@ -0,0 +1,86 @@ +package com.hiroshi.cimoc.source; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.SearchIterator; + +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by Hiroshi on 2017/3/21. + */ + +public class Null extends MangaParser { + + public static final int TYPE = -1; + public static final String DEFAULT_TITLE = "(null)"; + public static final String DEFAULT_SERVER = null; + + public Null() { + mTitle = DEFAULT_TITLE; + } + + @Override + public Request getSearchRequest(String keyword, int page) { + return null; + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + return null; + } + + @Override + public Request getInfoRequest(String cid) { + return null; + } + + @Override + public void parseInfo(String html, Comic comic) { + } + + @Override + public List parseChapter(String html) { + return null; + } + + @Override + public Request getImagesRequest(String cid, String path) { + return null; + } + + @Override + public List parseImages(String html) { + return null; + } + + @Override + public Request getCheckRequest(String cid) { + return null; + } + + @Override + public String parseCheck(String html) { + return null; + } + + @Override + public List parseCategory(String html, int page) { + return null; + } + + @Override + public Headers getHeader() { + return null; + } + + @Override + public String getUrl(String cid) { + return null; + } +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/Pic177.java b/app/src/main/java/com/hiroshi/cimoc/source/Pic177.java new file mode 100644 index 00000000..b9088231 --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/Pic177.java @@ -0,0 +1,105 @@ +package com.hiroshi.cimoc.source; + +/** + * Created by Hiroshi on 2016/10/5. + */ + +public class Pic177/* extends MangaParser */ { +/* + @Override + public Request getSearchRequest(String keyword, int page) { + String url = StringUtils.format("http://www.177pic66.com/page/%d?s=%s", page, keyword); + return new Request.Builder().url(url).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("#content > div.post_box")) { + @Override + protected Comic parse(Node node) { + String cid = node.hrefWithSubString("div.tit > h2 > a", 29); + String title = node.text("div.tit > h2 > a"); + String author = StringUtils.match("(\\[中文\\])?\\[(.*?)\\]", title, 2); + if (author != null) { + title = title.replaceFirst("(\\[中文\\])?\\[(.*?)\\]\\s*", ""); + } + String cover = node.src("div.c-con > a > img"); + String update = node.text("div.c-top > div.datetime").replace(" ", "-"); + return new Comic(SourceManager.SOURCE_177PIC, cid, title, cover, update, author); + } + }; + } + + @Override + public Request getInfoRequest(String cid) { + String url = "http://www.177pic66.com/html/".concat(cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) { + Node body = new Node(html); + String title = body.text("#content > div.post > div.c-top2 > div.tit > h1"); + String author = StringUtils.match("(\\[中文\\])?\\[(.*?)\\]", title, 2); + if (author != null) { + title = title.replaceFirst("(\\[中文\\])?\\[(.*?)\\]\\s*", ""); + } + String cover = body.src("#content > div.post > div.entry-content > p > img"); + String update = body.text("#content > div.post > div.c-top2 > div.datetime"); + comic.setInfo(title, cover, update, null, author, true); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + Node body = new Node(html); + int count = body.list("#single-navi span.single-navi").size(); + for (int i = count; i > 0; --i) { + list.add(new Chapter("Ch" + i, String.valueOf(i))); + } + return list; + } + + @Override + public Request getRecentRequest(int page) { + String url = "http://www.177pic66.com/page/" + page; + return new Request.Builder().url(url).build(); + } + + @Override + public List parseRecent(String html, int page) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list("div.conter > div.main > div.post_box")) { + String cid = node.hrefWithSubString("div.tit > h2 > a", 29); + String title = node.text("div.tit > h2 > a"); + String author = StringUtils.match("(\\[中文\\])?\\[(.*?)\\]", title, 2); + if (author != null) { + title = title.replaceFirst("(\\[中文\\])?\\[(.*?)\\]\\s*", ""); + } + String cover = node.src("div.c-con > a > img"); + String update = node.text("div.c-top > div.datetime").replace(" ", "-"); + list.add(new Comic(SourceManager.SOURCE_177PIC, cid, title, cover, update, author)); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("http://www.177pic66.com/html/%s/%s", cid, path); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + Node body = new Node(html); + int count = 0; + for (Node node : body.list("#content > div.post > div.entry-content > p > img")) { + list.add(new ImageUrl(++count, node.attr("src"), false)); + } + return list; + } +*/ +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/PuFei.java b/app/src/main/java/com/hiroshi/cimoc/source/PuFei.java new file mode 100644 index 00000000..b1fc2f7c --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/PuFei.java @@ -0,0 +1,213 @@ +package com.hiroshi.cimoc.source; + +import android.util.Pair; + +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaCategory; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.ui.activity.ResultActivity; +import com.hiroshi.cimoc.utils.DecryptionUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import okhttp3.Headers; +import okhttp3.Request; + +/** + * Created by FEILONG on 2017/12/21. + */ + +public class PuFei extends MangaParser { + + public static final int TYPE = 50; + public static final String DEFAULT_TITLE = "扑飞漫画"; + + public PuFei(Source source) { + init(source, new PuFei.Category()); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException { + String url = ""; + if (page == 1) { + url = StringUtils.format("http://m.pufei8.com/e/search/?searchget=1&tbname=mh&show=title,player,playadmin,bieming,pinyin,playadmin&tempid=4&keyboard=%s", + URLEncoder.encode(keyword, "GB2312")); + } + return new Request.Builder().url(url).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list("#detail > li")) { + @Override + protected Comic parse(Node node) { + Node node_a = node.list("a").get(0); + String cid = node_a.hrefWithSplit(1); + String title = node_a.text("h3"); + String cover = node_a.attr("div > img", "data-src"); + String author = node_a.text("dl > dd"); + String update = node.text("dl:eq(4) > dd"); + return new Comic(TYPE, cid, title, cover, update, author); + } + }; + } + + @Override + public String getUrl(String cid) { + return "http://m.pufei8.com/manhua/".concat(cid); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("m.pufei8.com")); + } + + @Override + public Request getInfoRequest(String cid) { + String url = "http://m.pufei8.com/manhua/".concat(cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + String title = body.text("div.main-bar > h1"); + String cover = body.src("div.book-detail > div.cont-list > div.thumb > img"); + String update = body.text("div.book-detail > div.cont-list > dl:eq(2) > dd"); + String author = body.text("div.book-detail > div.cont-list > dl:eq(3) > dd"); + String intro = body.text("#bookIntro"); + boolean status = isFinish(body.text("div.book-detail > div.cont-list > div.thumb > i")); + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + for (Node node : new Node(html).list("#chapterList2 > ul > li > a")) { + String title = node.attr("title"); + String path = node.hrefWithSplit(2); + list.add(new Chapter(title, path)); + } + return list; + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("http://m.pufei8.com/manhua/%s/%s.html", cid, path); + return new Request.Builder().url(url).build(); + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + String str = StringUtils.match("cp=\"(.*?)\"", html, 1); + if (str != null) { + try { + str = DecryptionUtils.evalDecrypt(DecryptionUtils.base64Decrypt(str)); + String[] array = str.split(","); + for (int i = 0; i != array.length; ++i) { + list.add(new ImageUrl(i + 1, "http://res.img.youzipi.net/" + array[i], false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return list; + } + + @Override + public Request getCheckRequest(String cid) { + return getInfoRequest(cid); + } + + @Override + public String parseCheck(String html) { + return new Node(html).text("div.book-detail > div.cont-list > dl:eq(2) > dd"); + } + + @Override + public List parseCategory(String html, int page) { + List list = new LinkedList<>(); + Node body = new Node(html); + for (Node node : body.list("div.cont-list > ul > li")) {//li > a + String cid = node.getChild("a").hrefWithSplit(1);//node.hrefWithSplit(1); + String title = node.text("a > h3"); + String cover = node.attr("a > div > img", "data-src"); + String update = node.text("dl:eq(4) > dd"); + String author = node.text("a > dl:eq(2) > dd"); + list.add(new Comic(TYPE, cid, title, cover, update, author)); + } + return list; + } + + @Override + public Headers getHeader() { + return Headers.of("Referer", "http://m.pufei8.com"); + } + + private static class Category extends MangaCategory { + + @Override + public boolean isComposite() { + return true; + } + + @Override + public String getFormat(String... args) { + return StringUtils.format("http://m.pufei.com/act/?act=list&page=%%d&catid=%s&ajax=1&order=%s", + args[CATEGORY_SUBJECT], args[CATEGORY_ORDER]); + } + + @Override + protected List> getSubject() { + List> list = new ArrayList<>(); +// list.add(Pair.create("全部", "")); + list.add(Pair.create("最近更新", "manhua/update")); + list.add(Pair.create("漫画排行", "manhua/paihang")); + list.add(Pair.create("少年热血", "shaonianrexue")); + list.add(Pair.create("武侠格斗", "wuxiagedou")); + list.add(Pair.create("科幻魔幻", "kehuan")); + list.add(Pair.create("竞技体育", "jingjitiyu")); + list.add(Pair.create("搞笑喜剧", "gaoxiaoxiju")); + list.add(Pair.create("侦探推理", "zhentantuili")); + list.add(Pair.create("恐怖灵异", "kongbulingyi")); + list.add(Pair.create("少女爱情", "shaonvaiqing")); + list.add(Pair.create("耽美BL", "danmeirensheng")); +// list.add(Pair.create("恋爱生活", "9")); + return list; + } + + @Override + protected boolean hasOrder() { + return true; + } + + @Override + protected List> getOrder() { + List> list = new ArrayList<>(); + list.add(Pair.create("发布", "index")); + list.add(Pair.create("更新", "update")); + list.add(Pair.create("人气", "view")); + return list; + } + + } + +} diff --git a/app/src/main/java/com/hiroshi/cimoc/source/Tencent.java b/app/src/main/java/com/hiroshi/cimoc/source/Tencent.java new file mode 100644 index 00000000..a052801d --- /dev/null +++ b/app/src/main/java/com/hiroshi/cimoc/source/Tencent.java @@ -0,0 +1,202 @@ +package com.hiroshi.cimoc.source; + +import com.google.common.collect.Lists; +import com.hiroshi.cimoc.model.Chapter; +import com.hiroshi.cimoc.model.Comic; +import com.hiroshi.cimoc.model.ImageUrl; +import com.hiroshi.cimoc.model.Source; +import com.hiroshi.cimoc.parser.MangaParser; +import com.hiroshi.cimoc.parser.NodeIterator; +import com.hiroshi.cimoc.parser.SearchIterator; +import com.hiroshi.cimoc.parser.UrlFilter; +import com.hiroshi.cimoc.soup.Node; +import com.hiroshi.cimoc.utils.DecryptionUtils; +import com.hiroshi.cimoc.utils.StringUtils; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import okhttp3.Headers; +import okhttp3.Request; + +import static com.hiroshi.cimoc.utils.DecryptionUtils.evalDecrypt; + +/** + * Created by FEILONG on 2017/12/21. + * need fix + */ + +public class Tencent extends MangaParser { + + public static final int TYPE = 51; + public static final String DEFAULT_TITLE = "腾讯动漫"; + + public Tencent(Source source) { + init(source, null); + } + + public static Source getDefaultSource() { + return new Source(null, DEFAULT_TITLE, TYPE, true); + } + + @Override + public Request getSearchRequest(String keyword, int page) throws UnsupportedEncodingException { + String url = ""; + if (page == 1) + url = "https://m.ac.qq.com/search/result?word=%s".concat(keyword); + return new Request.Builder().url(url).build(); + } + + @Override + public SearchIterator getSearchIterator(String html, int page) { + Node body = new Node(html); + return new NodeIterator(body.list(".comic-item")) { + @Override + protected Comic parse(Node node) { + String cid = node.attr("a", "href"); + cid = cid.substring("/comic/index/id/".length()); + String title = node.text(".comic-title"); + String cover = node.attr(".cover-image", "src"); + String update = node.text(".comic-update"); + String author = "UNKNOWN"; + return new Comic(TYPE, cid, title, cover, update, author); + } + }; + } + + @Override + public String getUrl(String cid) { + return "http://ac.qq.com/Comic/ComicInfo/id/".concat(cid); + } + + @Override + protected void initUrlFilterList() { + filter.add(new UrlFilter("ac.qq.com")); + filter.add(new UrlFilter("m.ac.qq.com")); + } + + @Override + public Request getInfoRequest(String cid) { + String url = "https://m.ac.qq.com/comic/index/id/".concat(cid); + return new Request.Builder().url(url).build(); + } + + @Override + public void parseInfo(String html, Comic comic) throws UnsupportedEncodingException { + Node body = new Node(html); + String title = body.text("div.head-title-tags > h1"); + String cover = body.src("div.head-banner > img"); + String update = ""; + String author = body.text("li.author-wr"); + String intro = body.text("div.head-info-desc"); + boolean status = isFinish("连载中");//todo: fix here + comic.setInfo(title, cover, update, intro, author, status); + } + + @Override + public Request getChapterRequest(String html, String cid) { + String url = "https://m.ac.qq.com/comic/chapterList/id/".concat(cid); + return new Request.Builder() + .url(url) + .build(); + } + + @Override + public List parseChapter(String html) { + List list = new LinkedList<>(); + for (Node node : new Node(html).list("ul.normal > li.chapter-item")) { + String title = node.text("a"); + String path = node.href("a").substring("/chapter/index/id/518333/cid/".length()); + list.add(new Chapter(title, path)); + } + return Lists.reverse(list); + } + + @Override + public Request getImagesRequest(String cid, String path) { + String url = StringUtils.format("https://m.ac.qq.com/chapter/index/id/%s/cid/%s", cid, path); + return new Request.Builder() + .url(url) + .build(); + } + + private String splice(String str, int from, int length) { + return str.substring(0, from) + str.substring(from + length, str.length()); + } + + private String decodeData(String str, String nonce) { + nonce = evalDecrypt(nonce); + Matcher m = Pattern.compile("\\d+[a-zA-Z]+").matcher(nonce); + final List matches = new ArrayList<>(); + while (m.find()) { + matches.add(m.group(0)); + } + int len = matches.size(); + while ((len--) != 0) { + str = splice(str, + Integer.parseInt(StringUtils.match("^\\d+", matches.get(len), 0)) & 255, + StringUtils.replaceAll(matches.get(len), "\\d+", "").length() + ); + } + return str; + } + + @Override + public List parseImages(String html) { + List list = new LinkedList<>(); + String str = StringUtils.match("data:\\s*'(.*)?',", html, 1); + if (str != null) { + try { + str = DecryptionUtils.base64Decrypt( + decodeData(str, StringUtils.match("