├── www ├── client │ ├── src │ │ ├── App.css │ │ ├── util │ │ │ ├── History.js │ │ │ ├── Toaster.js │ │ │ ├── ActivityLink.js │ │ │ ├── Function.js │ │ │ └── ABI.js │ │ ├── App.test.js │ │ ├── _template │ │ │ ├── component.js │ │ │ └── screen.js │ │ ├── component │ │ │ ├── Text.js │ │ │ ├── ScrollTopView.js │ │ │ ├── BackButton.js │ │ │ ├── UserAvatar.js │ │ │ ├── GroupNotice.js │ │ │ ├── SystemNoticeAction.js │ │ │ ├── FloatEditor.js │ │ │ ├── UserLink.js │ │ │ ├── QRImage.js │ │ │ ├── GroupCard.js │ │ │ ├── GroupListItem.js │ │ │ ├── MyTime.js │ │ │ ├── MessageItem.js │ │ │ ├── Cloumn3Layout.js │ │ │ ├── FeedText.js │ │ │ ├── UserMenu.js │ │ │ ├── BlacklistButton.js │ │ │ ├── LangIcon.js │ │ │ ├── UserCard.js │ │ │ ├── BlacklistItem.js │ │ │ ├── CommentItem.js │ │ │ ├── NoticeItem.js │ │ │ ├── Header.js │ │ │ └── BuyVipButton.js │ │ ├── index.js │ │ ├── screen │ │ │ ├── Test.js │ │ │ ├── FeedDetail.js │ │ │ ├── GroupList.js │ │ │ └── Notice.js │ │ ├── i18n.js │ │ ├── App.js │ │ └── registerServiceWorker.js │ ├── public │ │ ├── cover.png │ │ ├── logo.png │ │ ├── image │ │ │ ├── avatar.jpg │ │ │ └── background.jpg │ │ ├── usercard_cover.png │ │ ├── .htaccess │ │ ├── manifest.json │ │ ├── index.html │ │ └── locales │ │ │ └── zh-CN │ │ │ └── translations.json │ ├── .env.development │ ├── .env.production │ ├── .gitignore │ ├── config-overrides.js │ ├── vhost │ │ └── conf │ ├── README.md │ ├── package copy.json │ ├── package.json │ ├── RoboFile.php │ └── resource │ │ └── lianmilogo.svg ├── api │ ├── tests │ │ ├── _data │ │ │ ├── .gitkeep │ │ │ ├── data1.jpg │ │ │ └── group_cover.png │ │ ├── _output │ │ │ ├── .gitkeep │ │ │ ├── ApiCest.tryApi.fail.html │ │ │ ├── ApiCest.tryApi.fail.png │ │ │ ├── LoginCest.loginSuccessfully.fail.png │ │ │ ├── ApiCest.Register.fail.html │ │ │ ├── ApiCest.ReRegister.fail.html │ │ │ ├── ApiCest.getPaidFeedDetail.fail.html │ │ │ ├── ApiCest.joinGroup.fail.html │ │ │ ├── ApiCest.joinGroup2.fail.html │ │ │ ├── ApiCest.getGroupFeed.fail.html │ │ │ ├── ApiCest.feedPublish.fail.html │ │ │ ├── ApiCest.savePaidFeedComment4.fail.html │ │ │ ├── ApiCest.savePaidFeedComment2.fail.html │ │ │ ├── ApiCest.BadLogin.fail.html │ │ │ ├── ApiCest.getUserDetail.fail.html │ │ │ ├── ApiCest.feedUpdate.fail.html │ │ │ ├── ApiCest.saveFeedComment.fail.html │ │ │ ├── ApiCest.savePaidFeedComment.fail.html │ │ │ ├── ApiCest.Login.fail.html │ │ │ ├── ApiCest.getPaidFeedDetail4.fail.html │ │ │ └── ApiCest.getGroupFeed2.fail.html │ │ ├── api.suite.yml │ │ ├── _support │ │ │ ├── Helper │ │ │ │ ├── Api.php │ │ │ │ └── Acceptance.php │ │ │ ├── ApiTester.php │ │ │ └── AcceptanceTester.php │ │ ├── _loader.php │ │ └── LoginCest.php │ ├── .gitignore │ ├── contract │ │ ├── DeployedList.txt │ │ ├── src │ │ │ ├── LianMiTest.sol │ │ │ ├── LianMiGroupLite.sol │ │ │ └── LianMiGroup.sol │ │ ├── package.json │ │ ├── group.js │ │ ├── deploy.js │ │ └── build │ │ │ └── lianmi.abi │ ├── config.yaml │ ├── _lp │ │ ├── lib │ │ │ ├── Lazyphp │ │ │ │ └── Core │ │ │ │ │ ├── LmObject.php │ │ │ │ │ ├── LianmiException.php │ │ │ │ │ ├── Application.php │ │ │ │ │ ├── Datameta.php │ │ │ │ │ └── Ldo.php │ │ │ └── flight │ │ │ │ ├── autoload.php │ │ │ │ ├── Flight.php │ │ │ │ ├── net │ │ │ │ ├── Router.php │ │ │ │ └── Route.php │ │ │ │ ├── template │ │ │ │ └── View.php │ │ │ │ └── util │ │ │ │ └── Collection.php │ │ ├── config │ │ │ └── core.php │ │ └── lp.init.php │ ├── restart.online │ ├── sample.htaccess │ ├── behat.yml │ ├── vendor.change.md │ ├── i18n │ │ ├── jp │ │ │ └── translations.json │ │ └── zh-cn │ │ │ ├── SQLSTATE[42000].json │ │ │ └── translations.json │ ├── phpunit.xml │ ├── CODESTYLE.md │ ├── public │ │ ├── index.php │ │ ├── .htaccess │ │ └── route.php │ ├── .htaccess │ ├── features │ │ ├── user.feature │ │ └── bootstrap │ │ │ └── FeatureContext.php │ ├── composer.json │ ├── codeception.yml │ ├── migrations │ │ └── 20140422224730_lazy_php_db.php │ ├── upload2sae.php │ ├── config │ │ ├── database.php │ │ └── app.php │ ├── _build.php │ ├── phinx.yml │ ├── RoboFile.php │ ├── controller │ │ └── LazyphpController.php │ └── ws.server.php └── RoboFile.php ├── .DS_Store ├── .github └── FUNDING.yml ├── docker ├── db │ └── dockerfile └── app │ ├── dockerfile │ └── vhost.conf ├── images ├── 2021-08-25-21-20-33.png ├── 2021-08-25-21-21-47.png ├── 2021-08-25-21-22-24.png ├── 2021-08-25-21-23-12.png ├── 2021-08-25-21-24-27.png ├── 2021-08-25-21-28-14.png ├── 2021-08-25-21-31-27.png ├── 2021-08-25-21-32-18.png ├── 2021-08-25-21-33-05.png ├── 2021-08-25-21-34-44.png ├── 2021-08-25-21-36-09.png └── 2021-08-25-21-36-51.png ├── docker-compose.yml ├── README.cn.md └── README.md /www/client/src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/api/tests/_data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/api/tests/_output/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/api/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | storage/ 3 | contract/node_modules 4 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/.DS_Store -------------------------------------------------------------------------------- /www/api/contract/DeployedList.txt: -------------------------------------------------------------------------------- 1 | 0xf6351b9af2da7f8613c6763b42feedae6441f309 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: easychen 4 | -------------------------------------------------------------------------------- /docker/db/dockerfile: -------------------------------------------------------------------------------- 1 | FROM mariadb 2 | COPY notonlyfans.sql /docker-entrypoint-initdb.d/notonlyfans.sql -------------------------------------------------------------------------------- /www/api/tests/api.suite.yml: -------------------------------------------------------------------------------- 1 | actor: ApiTester 2 | modules: 3 | enabled: 4 | - \Helper\Api -------------------------------------------------------------------------------- /www/api/config.yaml: -------------------------------------------------------------------------------- 1 | handle: 2 | - rewrite: if (!is_file() && !is_dir() && path ~ "^/(.*)") goto "index.php/$1" -------------------------------------------------------------------------------- /www/client/public/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/www/client/public/cover.png -------------------------------------------------------------------------------- /www/client/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/www/client/public/logo.png -------------------------------------------------------------------------------- /images/2021-08-25-21-20-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/images/2021-08-25-21-20-33.png -------------------------------------------------------------------------------- /images/2021-08-25-21-21-47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/images/2021-08-25-21-21-47.png -------------------------------------------------------------------------------- /images/2021-08-25-21-22-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/images/2021-08-25-21-22-24.png -------------------------------------------------------------------------------- /images/2021-08-25-21-23-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/images/2021-08-25-21-23-12.png -------------------------------------------------------------------------------- /images/2021-08-25-21-24-27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/images/2021-08-25-21-24-27.png -------------------------------------------------------------------------------- /images/2021-08-25-21-28-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/images/2021-08-25-21-28-14.png -------------------------------------------------------------------------------- /images/2021-08-25-21-31-27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/images/2021-08-25-21-31-27.png -------------------------------------------------------------------------------- /images/2021-08-25-21-32-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/images/2021-08-25-21-32-18.png -------------------------------------------------------------------------------- /images/2021-08-25-21-33-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/images/2021-08-25-21-33-05.png -------------------------------------------------------------------------------- /images/2021-08-25-21-34-44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/images/2021-08-25-21-34-44.png -------------------------------------------------------------------------------- /images/2021-08-25-21-36-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/images/2021-08-25-21-36-09.png -------------------------------------------------------------------------------- /images/2021-08-25-21-36-51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/images/2021-08-25-21-36-51.png -------------------------------------------------------------------------------- /www/api/_lp/lib/Lazyphp/Core/LmObject.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/client/src/util/History.js: -------------------------------------------------------------------------------- 1 | import createHistory from 'history/createBrowserHistory'; 2 | export default createHistory() -------------------------------------------------------------------------------- /www/api/restart.online: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | pkill -f ws.server.php 3 | /usr/bin/php /data1/www/api.fi-mi.com/ws.server.php & -------------------------------------------------------------------------------- /www/api/tests/_data/group_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/www/api/tests/_data/group_cover.png -------------------------------------------------------------------------------- /www/client/public/image/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/www/client/public/image/avatar.jpg -------------------------------------------------------------------------------- /www/client/public/usercard_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/www/client/public/usercard_cover.png -------------------------------------------------------------------------------- /www/client/public/image/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/www/client/public/image/background.jpg -------------------------------------------------------------------------------- /www/api/contract/src/LianMiTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract LianMiGroupTest 4 | { 5 | uint public admin; 6 | 7 | } -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.tryApi.fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/www/api/tests/_output/ApiCest.tryApi.fail.png -------------------------------------------------------------------------------- /docker/app/dockerfile: -------------------------------------------------------------------------------- 1 | FROM webdevops/php-apache:7.4-alpine 2 | COPY vhost.conf /opt/docker/etc/httpd/vhost.conf 3 | RUN apk add npm 4 | RUN npm install -g yarn 5 | -------------------------------------------------------------------------------- /www/api/sample.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RewriteRule ^(.*)$ index.php [QSA,L] -------------------------------------------------------------------------------- /www/api/behat.yml: -------------------------------------------------------------------------------- 1 | default: 2 | # ... 3 | extensions: 4 | Behat\MinkExtension\Extension: 5 | base_url: 'http://jobdeer.sinaapp.com' 6 | goutte: ~ -------------------------------------------------------------------------------- /www/api/contract/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "ethereumjs-tx": "^1.3.5", 4 | "solc": "^0.4.24", 5 | "web3": "^1.0.0-beta.34" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /www/client/public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RewriteRule ^(.*)$ index.html [QSA,L] -------------------------------------------------------------------------------- /www/api/tests/_output/LoginCest.loginSuccessfully.fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/not-only-fans/HEAD/www/api/tests/_output/LoginCest.loginSuccessfully.fail.png -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.Register.fail.html: -------------------------------------------------------------------------------- 1 | {"message":"[INPUT]email\u5730\u5740\u5df2\u88ab\u6ce8\u518c","code":20001,"info":"email\u5730\u5740\u5df2\u88ab\u6ce8\u518c","args":[null]} -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.ReRegister.fail.html: -------------------------------------------------------------------------------- 1 | {"message":"[INPUT]email\u5730\u5740\u5df2\u88ab\u6ce8\u518c","code":20001,"info":"email\u5730\u5740\u5df2\u88ab\u6ce8\u518c","args":[null]} -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.getPaidFeedDetail.fail.html: -------------------------------------------------------------------------------- 1 | { 2 | "message": "[DATA]UID=6<<<", 3 | "code": 99999, 4 | "info": "UID=6<<<", 5 | "args": [ 6 | null 7 | ] 8 | } -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.joinGroup.fail.html: -------------------------------------------------------------------------------- 1 | { 2 | "message": "[AUTH]该栏目尚未启用或已被暂停", 3 | "code": 40001, 4 | "info": "该栏目尚未启用或已被暂停", 5 | "args": [ 6 | null 7 | ] 8 | } -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.joinGroup2.fail.html: -------------------------------------------------------------------------------- 1 | { 2 | "message": "[AUTH]该栏目尚未启用或已被暂停", 3 | "code": 40001, 4 | "info": "该栏目尚未启用或已被暂停", 5 | "args": [ 6 | null 7 | ] 8 | } -------------------------------------------------------------------------------- /www/client/.env.development: -------------------------------------------------------------------------------- 1 | REACT_APP_API_BASE = 'http://dd.ftqq.com:8088/' 2 | REACT_APP_WEBSOCKECT = 'ws://localhost:9501' 3 | REACT_APP_CONTRACT = '0xf6351b9af2da7f8613c6763b42feedae6441f309' 4 | -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.getGroupFeed.fail.html: -------------------------------------------------------------------------------- 1 | { 2 | "message": "[AUTH]只有成员才能查看栏目内容", 3 | "code": 40001, 4 | "info": "只有成员才能查看栏目内容", 5 | "args": [ 6 | null 7 | ] 8 | } -------------------------------------------------------------------------------- /www/client/.env.production: -------------------------------------------------------------------------------- 1 | REACT_APP_API_BASE = 'http://api.notonlyfans.vip/' 2 | REACT_APP_WEBSOCKECT = 'wss://api.notonlyfans.vip:9501' 3 | REACT_APP_CONTRACT = '0xf6351b9af2da7f8613c6763b42feedae6441f309' -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.feedPublish.fail.html: -------------------------------------------------------------------------------- 1 | { 2 | "message": "[INPUT]包含未被许可的图片链接,请重传图片后发布", 3 | "code": 20001, 4 | "info": "包含未被许可的图片链接,请重传图片后发布", 5 | "args": [ 6 | null 7 | ] 8 | } -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.savePaidFeedComment4.fail.html: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "message": "", 4 | "data": { 5 | "feed_id": "50", 6 | "text": "评论一下付费内容", 7 | "id": "36" 8 | } 9 | } -------------------------------------------------------------------------------- /www/api/vendor.change.md: -------------------------------------------------------------------------------- 1 | web3 默认的超时时间太短了,而且没有提供接口,只好改源码了 2 | vendor/sc0vu/Web3.php/src/Contract.php 111行 3 | $requestManager = new HttpRequestManager($provider); 4 | ↓ 5 | $requestManager = new HttpRequestManager($provider,10); -------------------------------------------------------------------------------- /www/api/i18n/jp/translations.json: -------------------------------------------------------------------------------- 1 | {"注册":"注册","邮件地址":"邮件地址","用户昵称":"用户昵称","昵称":"昵称","UserID":"UserID","用户ID,只能由英文、数字构成,全站唯一":"用户ID,只能由英文、数字构成,全站唯一","密码":"密码","登入密码,最短6位":"登入密码,最短6位","重复输入密码":"重复输入密码","再次输入密码确认":"再次输入密码确认","必填":"必填"} -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.savePaidFeedComment2.fail.html: -------------------------------------------------------------------------------- 1 | { 2 | "message": "[AUTH]没有权限查看或评论此内容,可使用有权限的账号登入后评论", 3 | "code": 40001, 4 | "info": "没有权限查看或评论此内容,可使用有权限的账号登入后评论", 5 | "args": [ 6 | null 7 | ] 8 | } -------------------------------------------------------------------------------- /www/api/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests/framework 5 | tests/app 6 | 7 | 8 | -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.BadLogin.fail.html: -------------------------------------------------------------------------------- 1 | {"message":"[INPUT]Email\u5730\u5740\u4e0d\u5b58\u5728\u6216\u8005\u5bc6\u7801\u9519\u8bef","code":20001,"info":"Email\u5730\u5740\u4e0d\u5b58\u5728\u6216\u8005\u5bc6\u7801\u9519\u8bef","args":[null]} -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.getUserDetail.fail.html: -------------------------------------------------------------------------------- 1 | { 2 | "message": "[INPUT]id格式不正确(经check_uint检查)vlaue=", 3 | "code": 20001, 4 | "info": "%s格式不正确(经%s检查)vlaue=", 5 | "args": [ 6 | "id", 7 | "check_uint" 8 | ] 9 | } -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.feedUpdate.fail.html: -------------------------------------------------------------------------------- 1 | { 2 | "message": "[INPUT]is_paid格式不正确(经check_uint检查)vlaue=", 3 | "code": 20001, 4 | "info": "%s格式不正确(经%s检查)vlaue=", 5 | "args": [ 6 | "is_paid", 7 | "check_uint" 8 | ] 9 | } -------------------------------------------------------------------------------- /www/api/tests/_support/Helper/Api.php: -------------------------------------------------------------------------------- 1 | { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.saveFeedComment.fail.html: -------------------------------------------------------------------------------- 1 | { 2 | "message": "[SQL]SELECT * FROM `group_member` WHERE `group_id` = '0' AND `uid` = '6' LIMIT 1", 3 | "code": 99999, 4 | "info": "SELECT * FROM `group_member` WHERE `group_id` = '0' AND `uid` = '6' LIMIT 1", 5 | "args": [ 6 | null 7 | ] 8 | } -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.savePaidFeedComment.fail.html: -------------------------------------------------------------------------------- 1 | { 2 | "message": "[SQL]SELECT * FROM `group_member` WHERE `group_id` = '0' AND `uid` = '6' LIMIT 1", 3 | "code": 99999, 4 | "info": "SELECT * FROM `group_member` WHERE `group_id` = '0' AND `uid` = '6' LIMIT 1", 5 | "args": [ 6 | null 7 | ] 8 | } -------------------------------------------------------------------------------- /www/api/CODESTYLE.md: -------------------------------------------------------------------------------- 1 | 编码规范 2 | ======== 3 | 4 | ## 自动加载及PSR4 5 | LP4采用Composer PSR4进行自动加载,第三方组件请使用自己的Namespace,做成独立的包即可。 6 | 7 | ## 函数命名 8 | my_method 9 | 10 | ## 方法命名 11 | myFirstMethod 12 | 13 | 14 | ## 文件和目录名 15 | 以.php结尾,目录和文件名采用驼峰写法分隔单词。 16 | 17 | ## 括号对齐 18 | ``` 19 | function name() 20 | { 21 | 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /www/client/src/util/Toaster.js: -------------------------------------------------------------------------------- 1 | import { Position, Toaster, Intent } from "@blueprintjs/core"; 2 | 3 | /** Singleton toaster instance. Create separate instances for different options. */ 4 | const AppToaster = Toaster.create({ 5 | className: "lm-toaster", 6 | position: Position.TOP_RIGHT 7 | }); 8 | 9 | export default AppToaster; -------------------------------------------------------------------------------- /www/api/public/index.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT, http://flightphp.com/license 7 | */ 8 | 9 | require_once __DIR__.'/core/Loader.php'; 10 | 11 | \flight\core\Loader::autoload(true, dirname(__DIR__)); 12 | -------------------------------------------------------------------------------- /www/api/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RewriteRule ^(.*)$ index.php [QSA,L] 5 | 6 | Header add Access-Control-Allow-Origin "*" 7 | Header add Access-Control-Allow-Headers "origin, x-requested-with, content-type" 8 | Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS" -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.Login.fail.html: -------------------------------------------------------------------------------- 1 | {"code":0,"message":"","data":{"id":"5","email":"easychen@gmail.com","username":"easychen","nickname":"Easy","level":"1","avatar":null,"group_count":"0","feed_count":"0","up_count":"0","timeline":"2018-07-30 09:59:26","address":"","cover":null,"uid":"5","token":"o82o0ptm609n02n9gge4o54uca","groups":[],"vip_groups":[],"admin_groups":[]}} -------------------------------------------------------------------------------- /www/client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /www/api/_lp/config/core.php: -------------------------------------------------------------------------------- 1 | 20001, 9 | 'AUTH'=>40001, 10 | 'NOTLOGIN'=>40301 11 | ]; 12 | -------------------------------------------------------------------------------- /www/api/features/user.feature: -------------------------------------------------------------------------------- 1 | Feature: User 2 | In order to know a user well 3 | As a api interface 4 | I need to get info via uid 5 | 6 | Scenario: get user info via uid 7 | Given I go to "/user/1" 8 | Then the response status code should be 200 9 | And I see the Json 10 | """ 11 | {"code":0,"message":"","data":{"id":"1","name":"easy","email":"easychen@gmail.com"}} 12 | """ -------------------------------------------------------------------------------- /www/client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /www/client/config-overrides.js: -------------------------------------------------------------------------------- 1 | const { 2 | override, 3 | addDecoratorsLegacy, 4 | disableEsLint, 5 | overrideDevServer, 6 | watchAll 7 | } = require("customize-cra"); 8 | 9 | module.exports = { 10 | webpack: override( 11 | addDecoratorsLegacy(), 12 | disableEsLint(), 13 | ), 14 | devServer: overrideDevServer( 15 | // dev server plugin 16 | watchAll() 17 | ) 18 | }; 19 | -------------------------------------------------------------------------------- /www/client/vhost/conf: -------------------------------------------------------------------------------- 1 | 2 | ServerAdmin easychen@gmail.com 3 | DocumentRoot "/var/www/fi-mi.com/build" 4 | ServerName fi-mi.com 5 | ErrorLog ${APACHE_LOG_DIR}/fi-mi.com.e.log 6 | CustomLog ${APACHE_LOG_DIR}/fi-mi.com.log common 7 | 8 | Options Indexes FollowSymLinks 9 | AllowOverride All 10 | Require all granted 11 | 12 | 13 | -------------------------------------------------------------------------------- /www/client/src/_template/component.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { withRouter } from 'react-router-dom'; 4 | import { translate } from 'react-i18next'; 5 | 6 | @withRouter 7 | @translate() 8 | @inject("store") 9 | @observer 10 | export default class ClassNamePlaceHolder extends Component 11 | { 12 | render() 13 | { 14 | return
ClassNamePlaceHolder
; 15 | } 16 | } -------------------------------------------------------------------------------- /www/client/src/component/Text.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { withRouter } from 'react-router-dom'; 4 | 5 | @inject("store") 6 | @observer 7 | export default class Text extends Component 8 | { 9 | render() 10 | { 11 | return
{this.props.store.count} 12 | 13 |
; 14 | } 15 | } -------------------------------------------------------------------------------- /www/client/src/component/ScrollTopView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | class ScrollTopView extends Component { 5 | componentDidUpdate(prevProps) { 6 | if (this.props.location !== prevProps.location) { 7 | // console.log('top...'); 8 | window.scrollTo(0, 0); 9 | } 10 | } 11 | 12 | render() { 13 | return null; 14 | } 15 | } 16 | 17 | export default ScrollTopView; -------------------------------------------------------------------------------- /www/RoboFile.php: -------------------------------------------------------------------------------- 1 | _exec("cd api/public && php -S 0.0.0.0:8088 route.php"); 13 | } 14 | 15 | public function devClient() 16 | { 17 | $this->_exec("cd client && yarn start"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /www/api/public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RewriteRule ^(.*)$ index.php [QSA,L] 5 | 6 | 7 | # SetEnvIf Origin "^http(s)?://(.+\.)?(fi\-mi\.com|api\.fi\-mi\.com|127\.0\.0\.1)$" origin_is=$0 8 | # Header always set Access-Control-Allow-Origin %{origin_is}e env=origin_is 9 | 10 | 11 | # Header always set Access-Control-Allow-Origin "*" 12 | 13 | # Header set Access-Control-Allow-Headers "origin, x-requested-with, content-type" 14 | # Header set Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS" -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | mariadb: 4 | build: ./docker/db 5 | volumes: 6 | - 'nof_mariadb_data:/var/lib/mysql' 7 | environment: 8 | # ALLOW_EMPTY_PASSWORD is recommended only for development. 9 | - MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=yes 10 | - MARIADB_SKIP_TEST_DB=yes 11 | - MARIADB_USER=root 12 | - MARIADB_DATABASE=notonlyfans 13 | app: 14 | build: ./docker/app 15 | depends_on: 16 | - mariadb 17 | ports: 18 | - '80:80' 19 | volumes: 20 | - './www:/app' 21 | 22 | volumes: 23 | nof_mariadb_data: 24 | driver: local -------------------------------------------------------------------------------- /www/client/src/component/BackButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { withRouter } from 'react-router-dom'; 4 | import { translate } from 'react-i18next'; 5 | import { Button } from "@blueprintjs/core"; 6 | 7 | @withRouter 8 | @translate() 9 | @inject("store") 10 | @observer 11 | export default class BackButton extends Component 12 | { 13 | render() 14 | { 15 | return this.props.history.length > 1 &&
16 |
; 18 | } 19 | } -------------------------------------------------------------------------------- /www/client/src/util/ActivityLink.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter as Router, Route, Link } from "react-router-dom"; 3 | import { Icon } from "@blueprintjs/core"; 4 | 5 | const ActivityLink = ({ label, to, activeOnlyWhenExact, icon, color }) => ( 6 | ( 10 |
11 | { icon &&  {label} } 12 | { !icon && {label} } 13 | 14 |
15 | )} 16 | /> 17 | ); 18 | 19 | export default ActivityLink; -------------------------------------------------------------------------------- /www/api/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": 3 | { 4 | "php":">=5.3.3", 5 | "league/flysystem": "^1.0", 6 | "pear/math_biginteger": "^1.0", 7 | "sc0vu/web3.php": "dev-master", 8 | "intervention/image": "^2.4" 9 | }, 10 | "require-dev": 11 | { 12 | "behat/behat": "2.4.*@stable", 13 | "behat/mink": "1.4@stable", 14 | "behat/mink-extension": "*", 15 | "behat/mink-goutte-driver": "1.0.*@dev", 16 | "crada/php-apidoc": "@dev" 17 | }, 18 | "autoload": 19 | { 20 | "psr-4": 21 | { 22 | "Lazyphp\\": "_lp/lib/Lazyphp/", 23 | "Lazyphp\\Controller\\": "controller/" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /www/client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | 7 | import { Provider } from "mobx-react"; 8 | import AppState from './store/AppState'; 9 | 10 | 11 | import './index.scss'; 12 | 13 | import { I18nextProvider } from 'react-i18next'; 14 | import i18n from './i18n'; // initialized i18next instance 15 | 16 | 17 | ReactDOM.render( 18 | 19 | 20 | 21 | , document.getElementById('root')); 22 | registerServiceWorker(); 23 | -------------------------------------------------------------------------------- /www/api/tests/_support/ApiTester.php: -------------------------------------------------------------------------------- 1 | 6 | ServerName notonlyfans.vip 7 | DocumentRoot "/app/client/build" 8 | 9 | Options FollowSymLinks MultiViews 10 | AllowOverride All 11 | Order allow,deny 12 | allow from all 13 | 14 | 15 | 16 | 17 | ServerName api.notonlyfans.vip 18 | DocumentRoot "/app/api/public" 19 | 20 | Options FollowSymLinks MultiViews 21 | AllowOverride All 22 | Order allow,deny 23 | allow from all 24 | 25 | 26 | -------------------------------------------------------------------------------- /www/api/tests/_loader.php: -------------------------------------------------------------------------------- 1 | : null; 18 | return content; 19 | } 20 | } -------------------------------------------------------------------------------- /www/client/src/component/GroupNotice.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { Link } from "react-router-dom"; 4 | import { withRouter } from 'react-router-dom'; 5 | import { translate } from 'react-i18next'; 6 | 7 | @translate() 8 | @inject("store") 9 | @withRouter 10 | @observer 11 | export default class GroupNotice extends Component 12 | { 13 | render() 14 | { 15 | const { user } = this.props.store; 16 | const { t } = this.props; 17 | 18 | return
19 | {t("没有可用的栏目,")} 20 | {t("创建栏目")} | {t("订阅栏目")} 21 |
; 22 | } 23 | } -------------------------------------------------------------------------------- /www/client/src/component/SystemNoticeAction.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { withRouter, Link } from 'react-router-dom'; 4 | import { translate } from 'react-i18next'; 5 | import UserAvatar from './UserAvatar'; 6 | import UserLink from './UserLink'; 7 | 8 | @withRouter 9 | @translate() 10 | @inject("store") 11 | @observer 12 | export default class SystemNoticeAction extends Component 13 | { 14 | render() 15 | { 16 | const { t } = this.props; 17 | const action = this.props.data ? this.props.data : null; 18 | 19 | return action &&
20 | {action.username} {t(action.action)} 21 |
; 22 | } 23 | } -------------------------------------------------------------------------------- /www/client/src/_template/screen.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { Link } from "react-router-dom"; 4 | import { withRouter } from 'react-router-dom'; 5 | import { translate } from 'react-i18next'; 6 | 7 | import Cloumn3Layout from '../component/Cloumn3Layout'; 8 | import UserCard from '../component/UserCard'; 9 | import DocumentTitle from 'react-document-title'; 10 | 11 | @withRouter 12 | @translate() 13 | @inject("store") 14 | @observer 15 | export default class ClassNamePlaceHolder extends Component 16 | { 17 | render() 18 | { 19 | const { t } = this.props; 20 | const main =
ClassNamePlaceHolder
; 21 | return } main={main} />; 22 | } 23 | } -------------------------------------------------------------------------------- /www/client/src/screen/Test.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { Link } from "react-router-dom"; 4 | import { withRouter } from 'react-router-dom'; 5 | import { translate } from 'react-i18next'; 6 | 7 | import Cloumn3Layout from '../component/Cloumn3Layout'; 8 | import UserCard from '../component/UserCard'; 9 | import DocumentTitle from 'react-document-title'; 10 | 11 | @withRouter 12 | @translate() 13 | @inject("store") 14 | @observer 15 | export default class Test extends Component 16 | { 17 | handleData(data) 18 | { 19 | console.log( data ); 20 | } 21 | 22 | render() 23 | { 24 | const { t } = this.props; 25 | const main =
; 26 | return } main={main} />; 27 | } 28 | } -------------------------------------------------------------------------------- /www/client/src/component/FloatEditor.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { withRouter } from 'react-router-dom'; 4 | import { translate } from 'react-i18next'; 5 | import { Overlay } from "@blueprintjs/core"; 6 | import PublishBox from '../component/PublishBox'; 7 | 8 | @withRouter 9 | @translate() 10 | @inject("store") 11 | @observer 12 | export default class FloatEditor extends Component 13 | { 14 | render() 15 | { 16 | const store = this.props.store; 17 | return {store.float_editor_open = !store.float_editor_open}}> 18 |
19 | {store.float_editor_open=false;}} onFinish={()=>{store.float_editor_open=false;}} /> 20 |
21 | 22 |
; 23 | } 24 | } -------------------------------------------------------------------------------- /www/api/_lp/lib/Lazyphp/Core/LianmiException.php: -------------------------------------------------------------------------------- 1 | info = $info; 18 | $this->args = $args; 19 | } 20 | 21 | public function __toString() 22 | { 23 | return get_class($this) . " '{$this->message}'"; 24 | } 25 | 26 | public function getInfo() 27 | { 28 | return $this->info; 29 | } 30 | 31 | public function getArgs() 32 | { 33 | return $this->args; 34 | } 35 | } -------------------------------------------------------------------------------- /www/api/codeception.yml: -------------------------------------------------------------------------------- 1 | # suite config 2 | suites: 3 | acceptance: 4 | actor: AcceptanceTester 5 | path: . 6 | modules: 7 | enabled: 8 | - WebDriver: 9 | url: http://localhost:3000 10 | port: 9515 11 | browser: chrome 12 | - \Helper\Acceptance 13 | 14 | api: 15 | actor: ApiTester 16 | path: . 17 | modules: 18 | enabled: 19 | - REST: 20 | url: http://localhost:8088 21 | depends: PhpBrowser 22 | 23 | extensions: 24 | enabled: [Codeception\Extension\RunFailed] 25 | 26 | params: 27 | - env 28 | 29 | gherkin: [] 30 | 31 | # additional paths 32 | paths: 33 | tests: tests 34 | output: tests/_output 35 | data: tests/_data 36 | support: tests/_support 37 | envs: tests/_envs 38 | 39 | settings: 40 | shuffle: false 41 | lint: true -------------------------------------------------------------------------------- /www/api/public/route.php: -------------------------------------------------------------------------------- 1 | table('lptest'); 18 | $table->addColumn('name', 'string' , array('limit' => 20) ) 19 | ->addColumn('password', 'string' , array('limit' => 20) ) 20 | ->addColumn('avatar', 'string' , array('limit' => 255) ) 21 | ->addColumn('created', 'datetime') 22 | ->create(); 23 | } 24 | 25 | 26 | 27 | /** 28 | * Migrate Up. 29 | */ 30 | public function up() 31 | { 32 | 33 | } 34 | 35 | /** 36 | * Migrate Down. 37 | */ 38 | public function down() 39 | { 40 | 41 | } 42 | } -------------------------------------------------------------------------------- /www/api/upload2sae.php: -------------------------------------------------------------------------------- 1 | {user2.nickname} : user2.nickname ): null; 21 | 22 | const admin = {t("SYSTEM")} 23 | 24 | // console.log( "user2" , user2 ); 25 | 26 | if( !user2 ) 27 | return null; 28 | else 29 | return toInt( user2.uid ) === 0 ? admin : content; 30 | } 31 | } -------------------------------------------------------------------------------- /www/api/config/database.php: -------------------------------------------------------------------------------- 1 | 'mysql', 5 | 'host' => 'mariadb', 6 | 'name' => 'notonlyfans', 7 | 'user' => 'root', 8 | 'password' => '', 9 | 'port' => 3306, 10 | 'charset' => 'utf8mb4' 11 | ); 12 | } else { 13 | $GLOBALS['lpconfig']['database'] = array( 14 | 'adapter' => 'mysql', 15 | 'host' => '127.0.0.1', 16 | 'name' => 'notonlyfans', 17 | 'user' => 'root', 18 | 'password' => '', 19 | 'port' => 3306, 20 | 'charset' => 'utf8mb4' 21 | ); 22 | } 23 | 24 | 25 | 26 | 27 | $GLOBALS['lpconfig']['database']['dsn'] = $GLOBALS['lpconfig']['database']['adapter'] 28 | .':host=' . $GLOBALS['lpconfig']['database']['host'] 29 | . ';port=' . $GLOBALS['lpconfig']['database']['port'] 30 | . ';dbname=' . $GLOBALS['lpconfig']['database']['name'] 31 | . ';charset=' . $GLOBALS['lpconfig']['database']['charset']; 32 | -------------------------------------------------------------------------------- /www/client/src/component/QRImage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { withRouter } from 'react-router-dom'; 4 | import QRCode from 'qrcode.react'; 5 | 6 | @withRouter 7 | @inject("store") 8 | @observer 9 | export default class QRImage extends Component 10 | { 11 | state = {"value":this.props.value,"img_url":""}; 12 | 13 | componentDidMount() 14 | { 15 | this.ck=setInterval( ()=>this.check() , 500 ); 16 | } 17 | 18 | check() 19 | { 20 | const canvas = document.getElementById('theqr__inbox'); 21 | if( canvas ) 22 | { 23 | this.setState({"img_url":canvas.toDataURL("image/png")}); 24 | clearInterval( this.ck ); 25 | } 26 | } 27 | 28 | 29 | render() 30 | { 31 | // 32 | return
33 | { this.state.img_url.length > 0 ? : } 34 |
35 | ; 36 | } 37 | } -------------------------------------------------------------------------------- /www/client/src/i18n.js: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import Backend from 'i18next-xhr-backend'; 3 | import LanguageDetector from 'i18next-browser-languagedetector'; 4 | import { reactI18nextModule } from 'react-i18next'; 5 | import axios from 'axios'; 6 | 7 | 8 | const dev_api = 'http://0.0.0.0:8088/'; 9 | 10 | i18n 11 | .use(Backend) 12 | .use(LanguageDetector) 13 | .use(reactI18nextModule) 14 | .init({ 15 | fallbackLng: 'en-US', 16 | lng: localStorage.getItem("i18nextLng") || "en-US", 17 | // have a common namespace used around the full app 18 | ns: ['translations'], 19 | defaultNS: 'translations', 20 | 21 | debug: false, 22 | saveMissing:false, 23 | missingKeyHandler: function(lng, ns, key) { 24 | //console.log(lng, ns, key); 25 | var params = new URLSearchParams(); 26 | params.append("lng" , JSON.stringify( lng )); 27 | params.append("ns" , ns ); 28 | params.append("key" , key ); 29 | const { data } = axios.post( dev_api + 'misswords' , params ); 30 | }, 31 | interpolation: { 32 | escapeValue: false, // not needed for react!! 33 | }, 34 | 35 | react: { 36 | wait: true 37 | } 38 | }); 39 | 40 | 41 | export default i18n; -------------------------------------------------------------------------------- /www/api/_lp/lib/flight/Flight.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT, http://flightphp.com/license 7 | */ 8 | 9 | /** 10 | * The Flight class is a static representation of the framework. 11 | */ 12 | class Flight { 13 | /** 14 | * Framework engine. 15 | * 16 | * @var object 17 | */ 18 | private static $engine; 19 | 20 | // Don't allow object instantiation 21 | private function __construct() {} 22 | private function __destruct() {} 23 | private function __clone() {} 24 | 25 | /** 26 | * Handles calls to static methods. 27 | * 28 | * @param string $name Method name 29 | * @param array $params Method parameters 30 | * @return mixed Callback results 31 | */ 32 | public static function __callStatic($name, $params) { 33 | static $initialized = false; 34 | 35 | if (!$initialized) { 36 | require_once __DIR__.'/autoload.php'; 37 | 38 | self::$engine = new \flight\Engine(); 39 | 40 | $initialized = true; 41 | } 42 | 43 | return \flight\core\Dispatcher::invokeMethod(array(self::$engine, $name), $params); 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /www/api/i18n/zh-cn/SQLSTATE[42000].json: -------------------------------------------------------------------------------- 1 | {"Syntax error or access violation. 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '`user` ( `email` , `nickname` , `username` , `password` ) VALUES ( 'easychen@gma' at line 1":"Syntax error or access violation. 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '`user` ( `email` , `nickname` , `username` , `password` ) VALUES ( 'easychen@gma' at line 1","Syntax error or access violation. 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '`user` ( `email` , `nickname` , `username` , `password` ) VALUES ( 'easychen@qq.' at line 1":"Syntax error or access violation. 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '`user` ( `email` , `nickname` , `username` , `password` ) VALUES ( 'easychen@qq.' at line 1","Syntax error or access violation. 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')' at line 1":"Syntax error or access violation. 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')' at line 1"} -------------------------------------------------------------------------------- /www/client/src/component/GroupCard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { Link } from "react-router-dom"; 4 | import { withRouter } from 'react-router-dom'; 5 | import { translate } from 'react-i18next'; 6 | import { Icon } from "@blueprintjs/core"; 7 | import Web3 from 'web3'; 8 | // import BuyVipButton from '../component/BuyVipButton'; 9 | 10 | @translate() 11 | @inject("store") 12 | @withRouter 13 | @observer 14 | export default class GroupCard extends Component 15 | { 16 | render() 17 | { 18 | const web3 = new Web3(Web3.givenProvider); 19 | const { t , group } = this.props; 20 | const price = group.price_wei && group.price_wei > 0 ? web3.utils.fromWei( group.price_wei + '' , 'ether' ) : 0; 21 | 22 | return
23 |
24 | 25 |

{group.name}

26 |
27 |
28 | 29 |
{t("订户")}{group.member_count}
30 |
{t("内容")}{group.feed_count}
31 |
{t("VIP")}{price}Ξ
32 | 33 |
34 |
; 35 | } 36 | } -------------------------------------------------------------------------------- /www/client/src/component/GroupListItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { withRouter, Link } from 'react-router-dom'; 4 | import { translate } from 'react-i18next'; 5 | import Web3 from 'web3'; 6 | import { Button, Icon } from "@blueprintjs/core"; 7 | 8 | @withRouter 9 | @translate() 10 | @inject("store") 11 | @observer 12 | export default class GroupListItem extends Component 13 | { 14 | constructor( props ) 15 | { 16 | super( props ); 17 | const data = this.props.data ? this.props.data : null; 18 | this.state = {"group":data}; 19 | } 20 | 21 | render() 22 | { 23 | const web3 = new Web3(Web3.givenProvider); 24 | const { t } = this.props; 25 | const item = this.state.group; 26 | if( !item ) return null; 27 | 28 | return
  • 29 |
    30 |
    31 |
    {item.name}
    32 |
    {web3.utils.fromWei( item.price_wei+'','ether' ) }Ξ ·  {item.member_count} {t("订户")} {item.feed_count > 0 &&  ·  {item.feed_count} {t("内容")}}
    33 |
    34 |
    35 |
    37 |
  • ; 38 | } 39 | } -------------------------------------------------------------------------------- /www/client/src/component/MyTime.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { withRouter } from 'react-router-dom'; 4 | import { translate } from 'react-i18next'; 5 | 6 | import TimeAgo from 'react-timeago'; 7 | import cnStrings from 'react-timeago/lib/language-strings/zh-CN'; 8 | import twStrings from 'react-timeago/lib/language-strings/zh-TW'; 9 | import enStrings from 'react-timeago/lib/language-strings/en'; 10 | import jaStrings from 'react-timeago/lib/language-strings/ja'; 11 | import buildFormatter from 'react-timeago/lib/formatters/buildFormatter'; 12 | 13 | @withRouter 14 | @translate() 15 | @inject("store") 16 | @observer 17 | export default class MyTime extends Component 18 | { 19 | render() 20 | { 21 | let formatter = buildFormatter(enStrings); 22 | switch( this.props.i18n.language.toLowerCase()) 23 | { 24 | case 'zh-cn' : 25 | formatter = buildFormatter(cnStrings); 26 | break; 27 | 28 | case 'zh-tw' : 29 | formatter = buildFormatter(twStrings); 30 | break; 31 | 32 | case 'jp' : 33 | formatter = buildFormatter(jaStrings); 34 | break; 35 | 36 | case 'en' : 37 | formatter = buildFormatter(enStrings); 38 | } 39 | 40 | 41 | 42 | return ; 43 | } 44 | } -------------------------------------------------------------------------------- /www/api/_build.php: -------------------------------------------------------------------------------- 1 | generate(); 50 | } catch (Exception $e) { 51 | echo 'There was an error generating the documentation: ', $e->getMessage(); 52 | } 53 | } 54 | 55 | 56 | -------------------------------------------------------------------------------- /www/api/features/bootstrap/FeatureContext.php: -------------------------------------------------------------------------------- 1 | getSession()->getPage()->getContent() ) != trim( (string)$string ) ) 41 | throw new Exception("Return data is ".$string); 42 | } 43 | 44 | // 45 | // Place your definition and hook methods here: 46 | // 47 | // /** 48 | // * @Given /^I have done something with "([^"]*)"$/ 49 | // */ 50 | // public function iHaveDoneSomethingWith($argument) 51 | // { 52 | // doSomethingWith($argument); 53 | // } 54 | // 55 | } 56 | -------------------------------------------------------------------------------- /www/client/README.md: -------------------------------------------------------------------------------- 1 | # NotOnlyFans前端说明文档 2 | 3 | 修改hooks。 4 | 5 | ## 添加页面 6 | 7 | ### 在 component 中添加页面,比如 index.js 8 | 9 | ``` 10 | import React, { Component } from 'react'; 11 | import { observer , inject } from 'mobx-react'; 12 | import { Redirect } from 'react-router-dom'; 13 | 14 | @inject("store") 15 | @observer 16 | export default class Index extends Component 17 | { 18 | render() 19 | { 20 | return

    Welcome to {this.props.store.appname}

    ; 21 | } 22 | } 23 | ``` 24 | 25 | 26 | ### 在 App.js 中 import 它 27 | 28 | ``` 29 | import Index from './component/Index'; 30 | ``` 31 | 32 | ### 并添加到 Switch 标签里边 33 | 34 | ``` 35 | 36 | ``` 37 | 38 | 这样就能访问到对应的页面了 39 | 40 | ## 添加全局数据和方法 41 | 42 | 只供单个组件使用的数据写到 state 里边即可;多个组件用的数据写到 store/AppState.js 里边。 43 | 44 | ``` 45 | @observable var-you-added = "EasyStarter"; 46 | ``` 47 | 48 | 之后可以直接在组件中调用: 49 | 50 | ``` 51 | {this.props.store.var-you-added} 52 | ``` 53 | 54 | 对网络请求、文件写入等异步操作,应该全部写入到 AppState.js 中。需要使用 @action 修饰符,建议使用 async/await 来处理异步。 55 | 56 | ``` 57 | @action 58 | async get_resume( id ) 59 | { 60 | var params = new URLSearchParams(); 61 | params.append("id" , id); 62 | const { data } = await axios.post( 'http://o.ftqq.com/?m=resume&a=detail' , params ); 63 | 64 | if( parseInt( data.code , 10 ) === 0 ) 65 | { 66 | this.current_resume_id = data.data.id; 67 | this.current_resume_title = data.data.title; 68 | this.current_resume_content = data.data.content; 69 | } 70 | return data ; 71 | } 72 | ``` 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /www/client/src/component/MessageItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { withRouter } from 'react-router-dom'; 4 | import { translate } from 'react-i18next'; 5 | import MyTime from './MyTime'; 6 | import Linkify from 'react-linkify'; 7 | import SystemNoticeAction from '../component/SystemNoticeAction'; 8 | import { toInt } from '../util/Function'; 9 | 10 | @withRouter 11 | @translate() 12 | @inject("store") 13 | @observer 14 | export default class MessageItem extends Component 15 | { 16 | state = {"message":null}; 17 | 18 | componentDidMount() 19 | { 20 | if( this.props.data ) 21 | this.setState({"message":this.props.data}); 22 | } 23 | 24 | render() 25 | { 26 | const item = this.state.message; 27 | if( !item ) return null; 28 | 29 | // console.log( 'item' , item ); 30 | 31 | const classname = item.uid == item.from_uid ? 'line right':'line left' 32 | return
    33 |
    34 | 35 | 36 | 37 | { toInt( item.from_uid ) !== 0 &&
    {item.text}
    } 38 | 39 | { toInt( item.from_uid ) === 0 &&
    } 40 | 41 | 42 | 43 |
    44 |
    45 | 46 |
    ; 47 | } 48 | } -------------------------------------------------------------------------------- /www/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | ... 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /www/client/package copy.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lianmiweb", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@blueprintjs/core": "^2.3.1", 7 | "axios": "^0.18.0", 8 | "fibos.js": "^0.3.11", 9 | "file-saver": "^2.0.2", 10 | "i18next": "^11.3.3", 11 | "i18next-browser-languagedetector": "^2.2.0", 12 | "i18next-xhr-backend": "^1.5.1", 13 | "mobx": "^5.0.1", 14 | "mobx-react": "^5.2.3", 15 | "mobx-react-devtools": "^5.0.1", 16 | "qrcode.react": "^1.0.0", 17 | "react": "^16.4.1", 18 | "react-avatar-editor": "^11.0.4", 19 | "react-cookie-consent": "^3.0.0", 20 | "react-document-title": "^2.0.3", 21 | "react-dom": "^16.4.1", 22 | "react-dropzone": "^4.2.12", 23 | "react-file-reader": "^1.1.4", 24 | "react-i18next": "^7.7.0", 25 | "react-linkify": "^0.2.2", 26 | "react-medium-image-zoom": "^3.1.2", 27 | "react-modal-image": "^2.5.0", 28 | "react-nl2br": "^0.4.0", 29 | "react-rnd": "^8.0.2", 30 | "react-router-dom": "^4.3.1", 31 | "react-scripts": "1.1.4", 32 | "react-select": "^2.0.0-beta.7", 33 | "react-timeago": "^4.1.9", 34 | "react-transition-group": "^2.3.1", 35 | "react-visibility-sensor": "^3.11.0", 36 | "sockette": "^2.0.0", 37 | "sprintf-js": "^1.1.1", 38 | "styled-components": "^3.3.2", 39 | "web3": "1.2.11" 40 | }, 41 | "scripts": { 42 | "start": "react-app-rewired start", 43 | "build": "react-app-rewired build", 44 | "test": "react-app-rewired test --env=jsdom", 45 | "eject": "react-scripts eject" 46 | }, 47 | "devDependencies": { 48 | "node-sass": "^4.9.0", 49 | "react-app-rewire-mobx": "^1.0.8", 50 | "react-app-rewire-sass-modules": "^1.1.5", 51 | "react-app-rewired": "^1.5.2", 52 | "sass-loader": "^7.0.3" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /www/client/src/component/Cloumn3Layout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { Link } from "react-router-dom"; 4 | import { withRouter } from 'react-router-dom'; 5 | import { translate } from 'react-i18next'; 6 | 7 | import Header from './Header'; 8 | import ScrollTopView from './ScrollTopView'; 9 | import FloatEditor from '../component/FloatEditor'; 10 | import ImBox from '../component/ImBox'; 11 | 12 | 13 | @translate() 14 | @inject("store") 15 | @withRouter 16 | @observer 17 | export default class Cloumn3Layout extends Component 18 | { 19 | 20 | componentDidMount() 21 | { 22 | let left_is_empty = true; 23 | 24 | window.document.querySelectorAll(".leftside > *").forEach( item =>{ 25 | if( window.getComputedStyle(item).display != "none" ) left_is_empty = false; 26 | }) 27 | 28 | if( left_is_empty && window.innerWidth <= 600 ) 29 | { 30 | window.document.querySelector(".leftside").style.display = "none"; 31 | window.scrollTo(0, 0); 32 | 33 | } 34 | } 35 | 36 | render() 37 | { 38 | const { left , right , main } = this.props; 39 | const show_im = this.props.store.im_open; 40 | 41 | 42 | return
    43 |
    44 |
    45 |
    46 |
    47 | {left} 48 |
    49 |
    50 | {main} 51 |
    52 |
    53 | {right} 54 |
    55 |
    56 |
    57 | { show_im && } 58 |
    ; 59 | } 60 | } -------------------------------------------------------------------------------- /www/client/src/screen/FeedDetail.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { Link } from "react-router-dom"; 4 | import { withRouter } from 'react-router-dom'; 5 | import { translate } from 'react-i18next'; 6 | 7 | import Cloumn3Layout from '../component/Cloumn3Layout'; 8 | import DocumentTitle from 'react-document-title'; 9 | import { toast, isApiOk, showApiError } from '../util/Function'; 10 | import FeedItem from '../component/FeedItem'; 11 | import { Button } from "@blueprintjs/core"; 12 | import BackButton from '../component/BackButton'; 13 | 14 | @withRouter 15 | @translate() 16 | @inject("store") 17 | @observer 18 | export default class FeedDetail extends Component 19 | { 20 | state = {"feed":null}; 21 | async componentDidMount() 22 | { 23 | const { t } = this.props; 24 | const id = this.props.match.params.id ? parseInt( this.props.match.params.id , 10 ) : 0 ; 25 | 26 | if( id < 1 ) 27 | { 28 | toast(t("无法获取内容ID,将转向到首页")); 29 | this.props.history.replace('/'); 30 | } 31 | 32 | const { data } = await this.props.store.getFeedDetail( id ); 33 | if( isApiOk( data ) ) 34 | { 35 | this.setState( {"feed":data.data} ); 36 | } 37 | else 38 | showApiError( data , t ); 39 | } 40 | 41 | render() 42 | { 43 | console.log( this.props.history , window.document.referrer ); 44 | 45 | const { t } = this.props; 46 | const item = this.state.feed; 47 | const main = item ?
    48 | 49 | 50 |
    :''; 51 | return ; 52 | } 53 | } -------------------------------------------------------------------------------- /www/client/src/component/FeedText.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { PropTypes } from 'prop-types'; 3 | import { strip } from '../util/Function'; 4 | import nl2br from 'react-nl2br'; 5 | import Linkify from 'react-linkify'; 6 | 7 | class FeedText extends Component { 8 | 9 | state = { 10 | expanded: false 11 | } 12 | 13 | toggleLines() 14 | { 15 | this.setState( { "expanded":!this.state.expanded } ); 16 | } 17 | 18 | render() { 19 | 20 | const maxlength = this.props.maxlength ? this.props.maxlength : 300; 21 | const toolong = this.props.text.length > maxlength ; 22 | const shorttext = this.props.text.substr( 0 , maxlength ); 23 | const longtext = this.props.text; 24 | const more = this.props.more ? this.props.more : 'more'; 25 | const less = this.props.less ? this.props.less : 'less'; 26 | const className = this.props.className ? this.props.className : ''; 27 | 28 | return ( 29 |
    30 | 31 | { toolong &&
    32 | { !this.state.expanded &&
    33 | {nl2br(shorttext)} 34 | ... this.toggleLines()}>{more} 35 |
    } 36 | 37 | { this.state.expanded &&
    38 | {nl2br(longtext)} this.toggleLines()}>{less} 39 |
    } 40 |
    } 41 | 42 | { !toolong &&
    {nl2br(longtext)}
    } 43 | 44 |
    45 | ); 46 | } 47 | } 48 | 49 | export default FeedText; -------------------------------------------------------------------------------- /www/api/tests/_output/ApiCest.getPaidFeedDetail4.fail.html: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "message": "", 4 | "data": { 5 | "id": "50", 6 | "uid": "5", 7 | "group_id": "0", 8 | "text": "这是一篇付费内容", 9 | "is_paid": "1", 10 | "files": null, 11 | "images": "[{\"orignal_url\":\"http:\\\/\\\/localhost:8088\\\/image\\\/u5\\\/2018.07.30.5b5eac1045096.jpg\",\"thumb_url\":\"http:\\\/\\\/localhost:8088\\\/image\\\/u5\\\/2018.07.30.5b5eac1045096.thumb.jpg\",\"type\":\"jpg\"}]", 12 | "timeline": "2018-07-30 14:11:28", 13 | "is_forward": "1", 14 | "forward_feed_id": "49", 15 | "forward_uid": "5", 16 | "forward_text": "", 17 | "forward_is_paid": "1", 18 | "forward_group_id": "5", 19 | "to_groups": "", 20 | "forward_timeline": "2018-07-30 14:11:28", 21 | "is_delete": "0", 22 | "comment_count": "1", 23 | "up_count": null, 24 | "user": { 25 | "id": "5", 26 | "username": "easychen", 27 | "nickname": "Easy", 28 | "level": "1", 29 | "avatar": null, 30 | "group_count": "1", 31 | "feed_count": "2", 32 | "up_count": "0", 33 | "timeline": "2018-07-30 14:11:27" 34 | }, 35 | "group": { 36 | "id": "5", 37 | "name": "第一个栏目", 38 | "author_uid": "5", 39 | "price_wei": "100000000", 40 | "author_address": "0xf05949e6d0ed5148843ce3f26e0f747095549bb4", 41 | "is_paid": "0", 42 | "is_active": "1", 43 | "cover": "http:\/\/localhost:8088\/image\/u5\/2018.07.30.5b5eac1019a33.png", 44 | "background": null, 45 | "seller_uid": "0", 46 | "timeline": "2018-07-30 14:11:28", 47 | "member_count": "2", 48 | "feed_count": "2", 49 | "todo_count": null, 50 | "promo_level": "0" 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /www/api/phinx.yml: -------------------------------------------------------------------------------- 1 | array 21 | ( 22 | 'migrations' => 'migrations' 23 | ), 24 | 'environments' => array 25 | ( 26 | 'default_migration_table' => 'phinxlog', 27 | 'default_database' => 'development', 28 | 'production' => array 29 | ( 30 | 'adapter' => $pro['adapter'], 31 | 'host' => $pro['host'], 32 | 'name' => $pro['name'], 33 | 'user' => $pro['user'], 34 | 'pass' => $pro['pass'], 35 | 'port' => $pro['port'], 36 | 'charset' => $pro['charset'] 37 | ), 38 | 'development' => array 39 | ( 40 | 'adapter' => $dev['adapter'], 41 | 'host' => $dev['host'], 42 | 'name' => $dev['name'], 43 | 'user' => $dev['user'], 44 | 'pass' => $dev['pass'], 45 | 'port' => $dev['port'], 46 | 'charset' => $dev['charset'] 47 | ), 48 | 'testing' => array 49 | ( 50 | 'adapter' => $dev['adapter'], 51 | 'host' => $dev['host'], 52 | 'name' => $dev['name'], 53 | 'user' => $dev['user'], 54 | 'pass' => $dev['pass'], 55 | 'port' => $dev['port'], 56 | 'charset' => $dev['charset'] 57 | ) 58 | ) 59 | ); 60 | 61 | //print_r( $config ); 62 | 63 | return $config; 64 | -------------------------------------------------------------------------------- /www/client/src/component/UserMenu.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { Link } from "react-router-dom"; 4 | import { withRouter } from 'react-router-dom'; 5 | import { translate } from 'react-i18next'; 6 | 7 | import { Menu , MenuItem, Popover, PopoverInteractionKind, Position, Icon } from "@blueprintjs/core"; 8 | import { isApiOk, toInt, toast, showApiError } from '../util/Function'; 9 | 10 | @translate() 11 | @inject("store") 12 | @withRouter 13 | @observer 14 | export default class UserMenu extends Component 15 | { 16 | async logout() 17 | { 18 | const { t } = this.props; 19 | const { data } = await this.props.store.logout(); 20 | if( isApiOk( data ) ) 21 | { 22 | if( toInt( data.data ) == 1 ) 23 | { 24 | toast(t("已成功退出")); 25 | this.props.history.replace('/login'); 26 | } 27 | }else 28 | showApiError( data , t ); 29 | } 30 | 31 | render() 32 | { 33 | const { t } = this.props; 34 | const { user } = this.props.store; 35 | 36 | 37 | return 38 | this.props.history.push('/settings/profile')} /> 39 | this.props.history.push('/settings/avatar')}/> 40 | this.props.history.push('/settings/password')}/> 41 | this.props.history.push('/settings/preference')}/> 42 | 43 | this.logout()}/> 44 | } position={Position.BOTTOM} interactionKind={PopoverInteractionKind.CLICK}> 45 | {user.nickname.substring(0,12)} 46 | ; 47 | } 48 | } -------------------------------------------------------------------------------- /www/client/src/component/BlacklistButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component,Fragment } from 'react'; 2 | import { observer , inject } from 'mobx-react'; 3 | import { withRouter } from 'react-router-dom'; 4 | import { translate } from 'react-i18next'; 5 | import { isApiOk, toInt, showApiError, toast } from '../util/Function'; 6 | import { Button } from "@blueprintjs/core"; 7 | 8 | @withRouter 9 | @translate() 10 | @inject("store") 11 | @observer 12 | export default class BlacklistButton extends Component 13 | { 14 | state = {"in":false} 15 | 16 | componentDidMount() 17 | { 18 | this.checkUser(); 19 | } 20 | 21 | async checkUser() 22 | { 23 | if( this.props.uid > 0 ) 24 | { 25 | const { data } = await this.props.store.checkUserInBlacklist( this.props.uid ); 26 | if( isApiOk( data ) ) 27 | { 28 | this.setState({"in":toInt( data.data ) ===1}) 29 | }else 30 | showApiError( data , this.props.t ); 31 | } 32 | 33 | } 34 | 35 | async setBlacklist( uid , status ) 36 | { 37 | const { t } = this.props; 38 | const { data } = await this.props.store.setUserInBlacklist( uid , status ); 39 | if( isApiOk( data ) ) 40 | { 41 | if( toInt( data.data ) === 1 ) 42 | toast(t("已将此用户加入黑名单,你可以在设置中移出")); 43 | else 44 | toast(t("已将此用户移出黑名单")); 45 | 46 | this.checkUser(); 47 | } 48 | else 49 | showApiError( data , t ); 50 | } 51 | 52 | render() 53 | { 54 | const { t } = this.props; 55 | return 56 | {this.state.in &&