From d39a4861c4f79f08f9f7a2da6d3061653d52f4a4 Mon Sep 17 00:00:00 2001 From: Lex Lim Date: Fri, 16 Jun 2023 06:36:19 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9B=A0=E4=B8=BA?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E7=B4=A2=E5=BC=95=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E7=AD=BE=E5=88=B0=E5=8A=9F=E8=83=BD=E5=BC=82=E5=B8=B8=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extension.json | 208 ++++++++++---------- includes/Hooks.php | 4 +- includes/Service/IsekaiUserPoints.php | 8 + modules/ext.isekai.userpoints.dailysign.js | 72 +++---- sql/mysql/isekai_user_daily_sign_log.sql | 4 +- sql/postgres/isekai_user_daily_sign_log.sql | 4 +- 6 files changed, 156 insertions(+), 144 deletions(-) diff --git a/extension.json b/extension.json index b06198e..ddf7ab1 100644 --- a/extension.json +++ b/extension.json @@ -1,105 +1,105 @@ -{ - "name": "Isekai User Points", - "namemsg": "isekai-userpoints", - "author": "Hyperzlib", - "version": "1.0.0", - "url": "https://git.isekai.cn/Isekai-Project/mediawiki-extension-IsekaiUserPoints", - "descriptionmsg": "isekai-userpoints-desc", - "license-name": "GPL-2.0-or-later", - "type": "api", - "requires": { - "MediaWiki": ">= 1.35.0" - }, - "MessagesDirs": { - "IsekaiUserPoints": [ - "i18n" - ] - }, - "ExtensionMessagesFiles": { - "IsekaiUserPointsAlias": "IsekaiUserPoints.alias.php" - }, - "AutoloadNamespaces": { - "Isekai\\UserPoints\\": "includes" - }, - "Hooks": { - "LoadExtensionSchemaUpdates": "Isekai\\UserPoints\\Hooks::onLoadExtensionSchemaUpdates", - "BeforePageDisplay": "Isekai\\UserPoints\\Hooks::onBeforePageDisplay", - "GetPreferences": "Isekai\\UserPoints\\Hooks::onGetPreferences" - }, - "APIModules": { - "userdailysign": "Isekai\\UserPoints\\Api\\ApiUserDailySign" - }, - "APIPropModules": { - "userpoints": "Isekai\\UserPoints\\Api\\ApiQueryUserPoints", - "pointinfo": "Isekai\\UserPoints\\Api\\ApiQueryPointInfo" - }, - "APIListModules": { - "userspoints": "Isekai\\UserPoints\\Api\\ApiQueryUsersPoints" - }, - "ServiceWiringFiles": [ - "IsekaiUserPoints.services.php" - ], - "GroupPermissions": { - "bureaucrat": { - "queryuserpoints": true, - "edituserpoints": true - }, - "suppress": { - "queryuserpoints": true, - "edituserpoints": true - }, - "sysop": { - "queryuserpoints": true, - "edituserpoints": true - } - }, - "GrantPermissions": { - "userpointsmanager": { - "queryuserpoints": true, - "edituserpoints": true - } - }, - "ResourceModules": { - "ext.isekai.userpoints.base": { - "styles": ["ext.isekai.userpoints.base.less"] - }, - "ext.isekai.userpoints.dailysign": { - "scripts": ["ext.isekai.userpoints.dailysign.js"], - "messages": [ - "comma-separator", - "isekai-userpoints-point-name-num", - "isekai-userpoints-dailysign-notify-title", - "isekai-userpoints-dailysign-notify-success" - ] - } - }, - "ResourceFileModulePaths": { - "localBasePath": "modules", - "remoteExtPath": "IsekaiUserPoints/modules" - }, - "config": { - "IsekaiUserPointConfig": { - "value": { - "exp": { - "name": "Exp.", - "namemsg": "isekai-userpoints-point-name-exp", - "icon": { - "normal": { - "image": "https://static.isekai.dev/isekaiwiki/isekaiwiki-exp.png" - }, - "invert": { - "image": "https://static.isekai.dev/isekaiwiki/isekaiwiki-exp-invert.png" - } - } - } - } - }, - "IsekaiUserPointShowOnUserPerferences": { - "value": true - }, - "IsekaiUserDailySignConfig": { - "value": null - } - }, - "manifest_version": 2 +{ + "name": "Isekai User Points", + "namemsg": "isekai-userpoints", + "author": "Hyperzlib", + "version": "1.0.0", + "url": "https://git.isekai.cn/Isekai-Project/mediawiki-extension-IsekaiUserPoints", + "descriptionmsg": "isekai-userpoints-desc", + "license-name": "GPL-2.0-or-later", + "type": "api", + "requires": { + "MediaWiki": ">= 1.35.0" + }, + "MessagesDirs": { + "IsekaiUserPoints": [ + "i18n" + ] + }, + "ExtensionMessagesFiles": { + "IsekaiUserPointsAlias": "IsekaiUserPoints.alias.php" + }, + "AutoloadNamespaces": { + "Isekai\\UserPoints\\": "includes" + }, + "Hooks": { + "LoadExtensionSchemaUpdates": "Isekai\\UserPoints\\Hooks::onLoadExtensionSchemaUpdates", + "BeforePageDisplay": "Isekai\\UserPoints\\Hooks::onBeforePageDisplay", + "GetPreferences": "Isekai\\UserPoints\\Hooks::onGetPreferences" + }, + "APIModules": { + "userdailysign": "Isekai\\UserPoints\\Api\\ApiUserDailySign" + }, + "APIPropModules": { + "userpoints": "Isekai\\UserPoints\\Api\\ApiQueryUserPoints", + "pointinfo": "Isekai\\UserPoints\\Api\\ApiQueryPointInfo" + }, + "APIListModules": { + "userspoints": "Isekai\\UserPoints\\Api\\ApiQueryUsersPoints" + }, + "ServiceWiringFiles": [ + "IsekaiUserPoints.services.php" + ], + "GroupPermissions": { + "bureaucrat": { + "queryuserpoints": true, + "edituserpoints": true + }, + "suppress": { + "queryuserpoints": true, + "edituserpoints": true + }, + "sysop": { + "queryuserpoints": true, + "edituserpoints": true + } + }, + "GrantPermissions": { + "userpointsmanager": { + "queryuserpoints": true, + "edituserpoints": true + } + }, + "ResourceModules": { + "ext.isekai.userpoints.base": { + "styles": ["ext.isekai.userpoints.base.less"] + }, + "ext.isekai.userpoints.dailysign": { + "scripts": ["ext.isekai.userpoints.dailysign.js"], + "messages": [ + "comma-separator", + "isekai-userpoints-point-name-num", + "isekai-userpoints-dailysign-notify-title", + "isekai-userpoints-dailysign-notify-success" + ] + } + }, + "ResourceFileModulePaths": { + "localBasePath": "modules", + "remoteExtPath": "IsekaiUserPoints/modules" + }, + "config": { + "IsekaiUserPointConfig": { + "value": { + "exp": { + "name": "Exp.", + "namemsg": "isekai-userpoints-point-name-exp", + "icon": { + "normal": { + "image": "https://static.isekai.dev/isekaiwiki/isekaiwiki-exp.png" + }, + "invert": { + "image": "https://static.isekai.dev/isekaiwiki/isekaiwiki-exp-invert.png" + } + } + } + } + }, + "IsekaiUserPointShowOnUserPerferences": { + "value": true + }, + "IsekaiUserDailySignConfig": { + "value": null + } + }, + "manifest_version": 2 } \ No newline at end of file diff --git a/includes/Hooks.php b/includes/Hooks.php index cfd33b8..5c2362b 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -24,10 +24,12 @@ class Hooks { } public static function onBeforePageDisplay(OutputPage $out, Skin $skin) { + $user = $out->getUser(); + $config = MediaWikiServices::getInstance()->getMainConfig(); $out->addModuleStyles(['ext.isekai.userpoints.base']); - + $dailySignConfig = $config->get('IsekaiUserDailySignConfig'); if ($dailySignConfig) { $out->addModules(['ext.isekai.userpoints.dailysign']); diff --git a/includes/Service/IsekaiUserPoints.php b/includes/Service/IsekaiUserPoints.php index ed10953..dd64cce 100644 --- a/includes/Service/IsekaiUserPoints.php +++ b/includes/Service/IsekaiUserPoints.php @@ -1,6 +1,7 @@ Utils::getPointName($this->type), + 'icon' => Utils::getPointIcon($this->type), + ]; + } + /** * Save point data */ diff --git a/modules/ext.isekai.userpoints.dailysign.js b/modules/ext.isekai.userpoints.dailysign.js index c25f6c6..0bfef71 100644 --- a/modules/ext.isekai.userpoints.dailysign.js +++ b/modules/ext.isekai.userpoints.dailysign.js @@ -1,43 +1,45 @@ $(function() { - const storeKey = 'isekai-userpoints-dailysign-lastSignDate'; - const lastSignDate = localStorage.getItem(storeKey); - const today = new Date().toLocaleDateString(); - if (lastSignDate !== today) { - let mwApi = new mw.Api(); - mwApi.postWithToken('csrf', { - action: 'userdailysign', - }).done(function(data) { - if (data.userdailysign && data.userdailysign.success) { - if (Array.isArray(data.userdailysign.point_delta)) { - const pointDelta = data.userdailysign.point_delta; - let pointMsgList = []; - pointDelta.forEach(function (pointDeltaInfo) { - let msg = mw.msg('isekai-userpoints-point-name-num', pointDeltaInfo.name, pointDeltaInfo.icon, pointDeltaInfo.points); - pointMsgList.push(msg); - }); - let separator = mw.msg('comma-separator'); - let pointMsg = pointMsgList.join(separator); + if (!mw.user.isAnon()) { + const storeKey = 'isekai-userpoints-dailysign-lastSignDate'; + const lastSignDate = localStorage.getItem(storeKey); + const today = new Date().toLocaleDateString(); + if (lastSignDate !== today) { + let mwApi = new mw.Api(); + mwApi.postWithToken('csrf', { + action: 'userdailysign', + }).done(function(data) { + if (data.userdailysign && data.userdailysign.success) { + if (Array.isArray(data.userdailysign.point_delta)) { + const pointDelta = data.userdailysign.point_delta; + let pointMsgList = []; + pointDelta.forEach(function (pointDeltaInfo) { + let msg = mw.msg('isekai-userpoints-point-name-num', pointDeltaInfo.name, pointDeltaInfo.icon, pointDeltaInfo.points); + pointMsgList.push(msg); + }); + let separator = mw.msg('comma-separator'); + let pointMsg = pointMsgList.join(separator); - let notificationMsg = mw.msg('isekai-userpoints-dailysign-notify-success', pointMsg); - mw.notify('', { - title: mw.msg('isekai-userpoints-dailysign-notify-title'), - tag: 'isekai-userpoints-dailysign', - id: 'isekai-userpoints-dailysign-notify', - }); + let notificationMsg = mw.msg('isekai-userpoints-dailysign-notify-success', pointMsg); + mw.notify('', { + title: mw.msg('isekai-userpoints-dailysign-notify-title'), + tag: 'isekai-userpoints-dailysign', + id: 'isekai-userpoints-dailysign-notify', + }); - function changeNotifyContent() { - let notifyDom = document.querySelector('#isekai-userpoints-dailysign-notify'); - if (notifyDom) { - notifyDom.querySelector('.mw-notification-content').innerHTML = notificationMsg; - } else { - requestAnimationFrame(changeNotifyContent); + function changeNotifyContent() { + let notifyDom = document.querySelector('#isekai-userpoints-dailysign-notify'); + if (notifyDom) { + notifyDom.querySelector('.mw-notification-content').innerHTML = notificationMsg; + } else { + requestAnimationFrame(changeNotifyContent); + } } - } - changeNotifyContent(); + changeNotifyContent(); + } + localStorage.setItem(storeKey, today); } - localStorage.setItem(storeKey, today); - } - }); + }); + } } }); \ No newline at end of file diff --git a/sql/mysql/isekai_user_daily_sign_log.sql b/sql/mysql/isekai_user_daily_sign_log.sql index 34417d7..620a3c6 100644 --- a/sql/mysql/isekai_user_daily_sign_log.sql +++ b/sql/mysql/isekai_user_daily_sign_log.sql @@ -4,7 +4,7 @@ CREATE TABLE /*_*/isekai_user_daily_sign_log ( `month` INT UNSIGNED NOT NULL, `sign_log` LONGTEXT NOT NULL ) /*$wgDBTableOptions*/; -ALTER TABLE /*_*/isekai_user_daily_sign_log ADD PRIMARY KEY (`user_id`); -ALTER TABLE /*_*/isekai_user_daily_sign_log ADD INDEX(`user_id`, `year`, `month`); +ALTER TABLE /*_*/isekai_user_daily_sign_log ADD PRIMARY KEY (`user_id`, `year`, `month`); +ALTER TABLE /*_*/isekai_user_daily_sign_log ADD INDEX(`user_id`); ALTER TABLE /*_*/isekai_user_daily_sign_log ADD INDEX(`year`); ALTER TABLE /*_*/isekai_user_daily_sign_log ADD INDEX(`month`); \ No newline at end of file diff --git a/sql/postgres/isekai_user_daily_sign_log.sql b/sql/postgres/isekai_user_daily_sign_log.sql index a17120d..32ce960 100644 --- a/sql/postgres/isekai_user_daily_sign_log.sql +++ b/sql/postgres/isekai_user_daily_sign_log.sql @@ -4,7 +4,7 @@ CREATE TABLE isekai_user_daily_sign_log ( month INT NOT NULL, sign_log JSON NOT NULL ); -ALTER TABLE isekai_user_daily_sign_log ADD PRIMARY KEY (user_id); -ALTER TABLE isekai_user_daily_sign_log ADD INDEX(user_id, year, month); +ALTER TABLE isekai_user_daily_sign_log ADD PRIMARY KEY (user_id, year, month); +ALTER TABLE isekai_user_daily_sign_log ADD INDEX(user_id); ALTER TABLE isekai_user_daily_sign_log ADD INDEX(year); ALTER TABLE isekai_user_daily_sign_log ADD INDEX(month); \ No newline at end of file From 50090fcf5b3733dd5b67c959736674009bada8ba Mon Sep 17 00:00:00 2001 From: Lex Lim Date: Sun, 16 Jul 2023 13:20:43 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Quests/IsekaiUserPointsQuests.alias.php | 2 + Quests/extension.json | 90 +++++++++++ Quests/i18n/zh-hans.json | 16 ++ .../Api/ApiUserQuestsGetNotification.php | 32 ++++ Quests/includes/Hooks.php | 79 +++++++++ Quests/includes/QuestsUtils.php | 152 ++++++++++++++++++ ...t.isekai.userpoints.quests.notification.js | 69 ++++++++ .../sql/mysql/isekai_user_quests_record.sql | 9 ++ .../postgres/isekai_user_quests_record.sql | 9 ++ extension.json | 9 +- i18n/zh-hans.json | 6 + includes/Hooks.php | 8 + modules/ext.isekai.userpoints.dailysign.js | 41 +++-- 13 files changed, 507 insertions(+), 15 deletions(-) create mode 100644 Quests/IsekaiUserPointsQuests.alias.php create mode 100644 Quests/extension.json create mode 100644 Quests/i18n/zh-hans.json create mode 100644 Quests/includes/Api/ApiUserQuestsGetNotification.php create mode 100644 Quests/includes/Hooks.php create mode 100644 Quests/includes/QuestsUtils.php create mode 100644 Quests/modules/ext.isekai.userpoints.quests.notification.js create mode 100644 Quests/sql/mysql/isekai_user_quests_record.sql create mode 100644 Quests/sql/postgres/isekai_user_quests_record.sql diff --git a/Quests/IsekaiUserPointsQuests.alias.php b/Quests/IsekaiUserPointsQuests.alias.php new file mode 100644 index 0000000..a6bcc64 --- /dev/null +++ b/Quests/IsekaiUserPointsQuests.alias.php @@ -0,0 +1,2 @@ += 1.39.0" + }, + "MessagesDirs": { + "IsekaiUserPointsQuests": [ + "i18n" + ] + }, + "ExtensionMessagesFiles": { + "IsekaiUserPointsQuestsAlias": "IsekaiUserPointsQuests.alias.php" + }, + "AutoloadNamespaces": { + "Isekai\\UserPoints\\Quests\\": "includes" + }, + "Hooks": { + "LoadExtensionSchemaUpdates": "Isekai\\UserPoints\\Quests\\Hooks::onLoadExtensionSchemaUpdates", + "PageSaveComplete": "Isekai\\UserPoints\\Quests\\Hooks::onPageSaveComplete", + "BeforePageDisplay": "Isekai\\UserPoints\\Quests\\Hooks::onBeforePageDisplay" + }, + "ResourceModules": { + "ext.isekai.userpoints.quests.notification": { + "scripts": ["ext.isekai.userpoints.quests.notification.js"], + "messages": [ + "comma-separator", + "isekai-userpoints-point-name-num", + "isekai-quests-complete-notification-title", + "isekai-quests-complete-notification-message", + "isekai-quests-daily-count", + "isekai-quests-weekly-count", + "isekai-quests-monthly-count", + "isekai-quests-type-edit-notification", + "isekai-quests-type-create-notification" + ] + } + }, + "ResourceFileModulePaths": { + "localBasePath": "modules", + "remoteExtPath": "IsekaiUserPoints/Quests/modules" + }, + "APIModules": { + "userquestsgetnotification": "Isekai\\UserPoints\\Quests\\Api\\ApiUserQuestsGetNotification" + }, + "config": { + "IsekaiQuestsConfig": { + "value": { + "new": { + "periodQuestNumber": 1, + "periodType": "daily", + "points": { + "exp": { + "points": [60] + } + } + }, + "edit": { + "periodQuestNumber": 2, + "periodType": "daily", + "points": { + "exp": { + "points": [40, 40] + } + } + } + } + }, + "IsekaiQuestsTaskConfig": { + "value": { + "edit": { + "unitPoints": { + "exp": 10 + }, + "maxPoints": { + "exp": 400 + }, + "unitWords": 100 + } + } + } + }, + "manifest_version": 2 +} \ No newline at end of file diff --git a/Quests/i18n/zh-hans.json b/Quests/i18n/zh-hans.json new file mode 100644 index 0000000..e9ad4af --- /dev/null +++ b/Quests/i18n/zh-hans.json @@ -0,0 +1,16 @@ +{ + "isekai-userpoints-quests": "异世界百科 用户积分 任务系统", + "isekai-userpoints-quests-desc": "用户完成对应任务后自动获取积分。", + + "apihelp-userquestsgetnotification-summary": "获取用户任务完成的通知消息", + + "isekai-quests-complete-notification-title": "任务完成", + "isekai-quests-complete-notification-message": "$1,获得了$2$3", + + "isekai-quests-daily-count": "(今日 $1/$2)", + "isekai-quests-weekly-count": "(本周 $1/$2)", + "isekai-quests-monthly-count": "(本月 $1/$2)", + + "isekai-quests-type-edit-notification": "编辑了页面", + "isekai-quests-type-create-notification": "创建了页面" +} \ No newline at end of file diff --git a/Quests/includes/Api/ApiUserQuestsGetNotification.php b/Quests/includes/Api/ApiUserQuestsGetNotification.php new file mode 100644 index 0000000..953c72e --- /dev/null +++ b/Quests/includes/Api/ApiUserQuestsGetNotification.php @@ -0,0 +1,32 @@ +getMain(), $method ); + } + + public function execute() { + $user = $this->getUser(); + + if (!$user->isRegistered()) { + $this->dieWithError('apierror-mustbeloggedin-generic', 'login-required'); + } + + $sesssionManager = SessionManager::getGlobalSession(); + if ($sesssionManager->exists(QuestsUtils::SESSION_KEY_QUEST_COMPLETE_NOTIFICATION)) { + $notificationData = $sesssionManager->get(QuestsUtils::SESSION_KEY_QUEST_COMPLETE_NOTIFICATION); + $this->getResult()->addValue(['userquestsgetnotification'], 'notification', $notificationData); + } else { + $this->getResult()->addValue(['userquestsgetnotification'], 'notification', null); + } + } + + public function isInternal() { + return true; + } +} \ No newline at end of file diff --git a/Quests/includes/Hooks.php b/Quests/includes/Hooks.php new file mode 100644 index 0000000..1e3c249 --- /dev/null +++ b/Quests/includes/Hooks.php @@ -0,0 +1,79 @@ +getDB()->getType(); + $updater->addExtensionTable('isekai_user_quests_record', $dir . $type . '/isekai_user_quests_record.sql'); + } + + /** + * @param \WikiPage $wikiPage + * @param \MediaWiki\User\UserIdentity $user + * @param string $summary + * @param int $flags + * @param \MediaWiki\Revision\RevisionRecord $revisionRecord + * @param \MediaWiki\Storage\EditResult $editResult + */ + public static function onPageSaveComplete($wikiPage, $user, $summary, $flags, $revisionRecord, $editResult) { + wfDebugLog('IsekaiQuests', 'onPageSaveComplete'); + $services = MediaWikiServices::getInstance(); + $config = $services->getMainConfig(); + + $questsConfig = $config->get('IsekaiQuestsConfig'); + + $allowedNamespaces = [NS_MAIN, NS_CATEGORY, NS_TEMPLATE, NS_HELP]; + + if (in_array($wikiPage->getNamespace(), $allowedNamespaces)) { + if ($flags & EDIT_NEW && isset($questsConfig['new'])) { + // 新建页面 + $questConfig = $questsConfig['new']; + + $questRes = QuestsUtils::onQuestComplete($user, 'new', $questConfig); + wfDebugLog('IsekaiQuests', 'quest finished: ', var_export($questRes, true)); + if ($questRes) { + QuestsUtils::setQuestCompleteNotification('new', $questRes['deltaPoints'], $questRes['completeNum'], $questConfig); + return true; + } + } + + // 如果新建页面的任务次数已满,则作为编辑页面处理 + if ($flags & EDIT_UPDATE && isset($questsConfig['edit'])) { + // 编辑页面 + $questConfig = $questsConfig['edit']; + + $questRes = QuestsUtils::onQuestComplete($user, 'edit', $questConfig); + wfDebugLog('IsekaiQuests', 'quest finished: ', var_export($questRes, true)); + if ($questRes) { + QuestsUtils::setQuestCompleteNotification('edit', $questRes['deltaPoints'], $questRes['completeNum'], $questConfig); + return true; + } + } + } + + return true; + } + + public static function onBeforePageDisplay(OutputPage $out, $skin) { + $out->addModules(['ext.isekai.userpoints.quests.notification']); + + $sessionManager = SessionManager::getGlobalSession(); + if ($sessionManager->exists(QuestsUtils::SESSION_KEY_QUEST_COMPLETE_NOTIFICATION)) { + $out->addJsConfigVars([ + 'wgIsekaiQuestsCompleteNotification' => $sessionManager->get(QuestsUtils::SESSION_KEY_QUEST_COMPLETE_NOTIFICATION), + ]); + $sessionManager->remove(QuestsUtils::SESSION_KEY_QUEST_COMPLETE_NOTIFICATION); + } + } +} diff --git a/Quests/includes/QuestsUtils.php b/Quests/includes/QuestsUtils.php new file mode 100644 index 0000000..693ec41 --- /dev/null +++ b/Quests/includes/QuestsUtils.php @@ -0,0 +1,152 @@ +getDBLoadBalancer()->getConnection(DB_REPLICA); + + $res = $dbr->newSelectQueryBuilder() + ->from('isekai_user_quests_record') + ->where([ + 'user_id' => $user->getId(), + 'quest_type' => $questType, + 'period_time' => $periodTime, + ])->field('quest_complete_num') + ->fetchField(); + + if ($res === false) { + return 0; + } + + return intval($res); + } + + public static function setUserQuestCompleteNum(User $user, string $questType, string $periodTime, int $num) { + $dbr = MediaWikiServices::getInstance()->getDBLoadBalancer()->getConnection(DB_REPLICA); + $dbw = MediaWikiServices::getInstance()->getDBLoadBalancer()->getConnection(DB_PRIMARY); + + $res = $dbr->selectField('isekai_user_quests_record', 'id', [ + 'user_id' => $user->getId(), + 'quest_type' => $questType, + 'period_time' => $periodTime, + ], __METHOD__); + + if ($res === false) { + $dbw->insert('isekai_user_quests_record', [ + 'user_id' => $user->getId(), + 'quest_type' => $questType, + 'period_time' => $periodTime, + 'quest_complete_num' => $num, + ], __METHOD__); + } else { + $dbw->update('isekai_user_quests_record', [ + 'quest_complete_num' => $num, + ], [ + 'id' => $res, + ], __METHOD__); + } + } + + public static function onQuestComplete(User $user, string $questType, array $questConfig) { + $services = MediaWikiServices::getInstance(); + + $periodQuestNumber = $questConfig['periodQuestNumber']; + $periodType = $questConfig['periodType']; + $pointsConfig = $questConfig['points']; + + $periodTime = QuestsUtils::getPeriodTime($periodType); + + $completeNum = QuestsUtils::getUserQuestCompleteNum($user, $questType, $periodTime); + + if ($completeNum > $periodQuestNumber) { + return false; + } + + $deltaPoints = []; + + /** @var IsekaiUserPointsFactory */ + $userPointsFactory = $services->getService('IsekaiUserPoints'); + foreach ($pointsConfig as $pointType => $pointConfig) { + $userPoints = $userPointsFactory->newFromUser($user, $pointType); + + if ($userPoints) { + $expireTime = $pointConfig['expireTime'] ?? -1; + $pointsAddConfig = $pointConfig['points']; + + if (count($pointsAddConfig) > 0) { + // 获取当前任务完成次数对应的积分数量 + $periodIndex = $completeNum % count($pointsAddConfig); + $pointsAdd = $pointsAddConfig[$periodIndex]; + + $userPoints->addPoints($pointsAdd, $expireTime, 'quests', $questType); + + $deltaPoints[$pointType] = $pointsAdd; + } + } + } + + $completeNum ++; + QuestsUtils::setUserQuestCompleteNum($user, $questType, $periodTime, $completeNum); + + return [ + 'completeNum' => $completeNum, + 'deltaPoints' => $deltaPoints, + ]; + } + + /** + * Get period time string from period type + * @param string $periodType + * @return string + */ + public static function getPeriodTime($periodType = 'daily') { + switch ($periodType) { + case 'daily': + return date('Y-m-d'); + case 'weekly': + return date('Y-W') . 'week'; + case 'monthly': + return date('Y-m'); + } + } + + public static function setQuestCompleteNotification(string $questType, array $deltaPoints, $completeNum = null, ?array $questConfig = null) { + $sesssionManager = SessionManager::getGlobalSession(); + + $resultPointDelta = []; + foreach ($deltaPoints as $pointType => $delta) { + $pointName = Utils::getPointName($pointType); + $pointIcon = Utils::getPointIcon($pointType); + + $resultPointDelta[] = [ + 'point_type' => $pointType, + 'name' => $pointName, + 'icon' => $pointIcon, + 'points' => $delta, + ]; + } + + $notificationData = [ + 'questType' => $questType, + 'deltaPoints' => $resultPointDelta, + ]; + + if ($questConfig) { + $notificationData['completeNum'] = $completeNum; + $notificationData['periodQuestNumber'] = $questConfig['periodQuestNumber']; + $notificationData['periodType'] = $questConfig['periodType']; + } + + $sesssionManager->set(self::SESSION_KEY_QUEST_COMPLETE_NOTIFICATION, $notificationData); + } +} diff --git a/Quests/modules/ext.isekai.userpoints.quests.notification.js b/Quests/modules/ext.isekai.userpoints.quests.notification.js new file mode 100644 index 0000000..2d459b1 --- /dev/null +++ b/Quests/modules/ext.isekai.userpoints.quests.notification.js @@ -0,0 +1,69 @@ +function onQuestsCompleteNotification(notificationData) { + var questType = notificationData.questType; + var deltaPoints = notificationData.deltaPoints; + var completeNum = notificationData.completeNum; + var periodQuestNumber = notificationData.periodQuestNumber; + var periodType = notificationData.periodType; + + var pointMsgList = []; + deltaPoints.forEach(function (pointDeltaInfo) { + var msg = mw.msg('isekai-userpoints-point-name-num', pointDeltaInfo.name, pointDeltaInfo.icon, pointDeltaInfo.points); + pointMsgList.push(msg); + + var separator = mw.msg('comma-separator'); + var pointMsg = pointMsgList.join(separator); + + var actionMsg = mw.msg('isekai-quests-type-' + questType + '-notification'); + + var completeNumMsg = ''; + switch (periodType) { + case 'daily': + completeNumMsg = mw.msg('isekai-quests-daily-count', completeNum, periodQuestNumber); + break; + case 'weekly': + completeNumMsg = mw.msg('isekai-quests-weekly-count', completeNum, periodQuestNumber); + break; + case 'monthly': + completeNumMsg = mw.msg('isekai-quests-monthly-count', completeNum, periodQuestNumber); + break; + } + + var notificationMsg = mw.msg('isekai-quests-complete-notification-message', actionMsg, pointMsg, completeNumMsg); + + mw.notify('', { + title: mw.msg('isekai-quests-complete-notification-title'), + tag: 'isekai-userpoints-quest', + id: 'isekai-userpoints-quest-notify', + }); + + function changeNotifyContent() { + var notifyDom = document.querySelector('#isekai-userpoints-quest-notify'); + if (notifyDom) { + notifyDom.querySelector('.mw-notification-content').innerHTML = notificationMsg; + } else { + requestAnimationFrame(changeNotifyContent); + } + } + + changeNotifyContent(); + }); +} + +mw.hook('postEdit').add(function (data) { + console.log('onPostEdit'); + + var notificationData = mw.config.get('wgIsekaiQuestsCompleteNotification'); + if (notificationData) { + onQuestsCompleteNotification(notificationData); + } else { + // VE,通过API获取消息 + var mwApi = new mw.Api(); + mwApi.get({ + action: 'userquestsgetnotification', + }).done(function (data) { + if (data.userquestsgetnotification && data.userquestsgetnotification.notification) { + onQuestsCompleteNotification(data.userquestsgetnotification.notification); + } + }); + } +}); \ No newline at end of file diff --git a/Quests/sql/mysql/isekai_user_quests_record.sql b/Quests/sql/mysql/isekai_user_quests_record.sql new file mode 100644 index 0000000..204377a --- /dev/null +++ b/Quests/sql/mysql/isekai_user_quests_record.sql @@ -0,0 +1,9 @@ +CREATE TABLE /*_*/isekai_user_quests_record ( + `id` INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + `user_id` INT UNSIGNED NOT NULL, + `quest_type` VARCHAR(60) NOT NULL, + `period_time` VARCHAR(50) NOT NULL, + `quest_complete_num` INT UNSIGNED NOT NULL DEFAULT '0' +) /*$wgDBTableOptions*/; +ALTER TABLE /*_*/isekai_user_quests_record ADD INDEX (`user_id`, `quest_type`); +ALTER TABLE /*_*/isekai_user_quests_record ADD INDEX (`user_id`); \ No newline at end of file diff --git a/Quests/sql/postgres/isekai_user_quests_record.sql b/Quests/sql/postgres/isekai_user_quests_record.sql new file mode 100644 index 0000000..c21f13a --- /dev/null +++ b/Quests/sql/postgres/isekai_user_quests_record.sql @@ -0,0 +1,9 @@ +CREATE TABLE isekai_user_quests_record ( + id SERIAL PRIMARY KEY, + user_id INT NOT NULL, + quest_type VARCHAR(60) NOT NULL, + period_time VARCHAR(50) NOT NULL, + quest_complete_num INT NOT NULL DEFAULT 0 +); +ALTER TABLE isekai_user_points_log ADD INDEX (user_id, quest_type); +ALTER TABLE isekai_user_points_log ADD INDEX (user_id); \ No newline at end of file diff --git a/extension.json b/extension.json index ddf7ab1..e9b4ac2 100644 --- a/extension.json +++ b/extension.json @@ -1,14 +1,14 @@ { - "name": "Isekai User Points", + "name": "IsekaiUserPoints", "namemsg": "isekai-userpoints", "author": "Hyperzlib", "version": "1.0.0", "url": "https://git.isekai.cn/Isekai-Project/mediawiki-extension-IsekaiUserPoints", "descriptionmsg": "isekai-userpoints-desc", - "license-name": "GPL-2.0-or-later", + "license-name": "MIT", "type": "api", "requires": { - "MediaWiki": ">= 1.35.0" + "MediaWiki": ">= 1.39.0" }, "MessagesDirs": { "IsekaiUserPoints": [ @@ -24,7 +24,8 @@ "Hooks": { "LoadExtensionSchemaUpdates": "Isekai\\UserPoints\\Hooks::onLoadExtensionSchemaUpdates", "BeforePageDisplay": "Isekai\\UserPoints\\Hooks::onBeforePageDisplay", - "GetPreferences": "Isekai\\UserPoints\\Hooks::onGetPreferences" + "GetPreferences": "Isekai\\UserPoints\\Hooks::onGetPreferences", + "ResourceLoaderGetConfigVars": "Isekai\\UserPoints\\Hooks::onResourceLoaderGetConfigVars" }, "APIModules": { "userdailysign": "Isekai\\UserPoints\\Api\\ApiUserDailySign" diff --git a/i18n/zh-hans.json b/i18n/zh-hans.json index 1c09171..08eafe0 100644 --- a/i18n/zh-hans.json +++ b/i18n/zh-hans.json @@ -2,6 +2,12 @@ "isekai-userpoints": "异世界百科 用户积分", "isekai-userpoints-desc": "存储用户积分,为其他应用提供积分功能", + "action-queryuserpoints": "查询其他用户的积分", + "right-queryuserpoints": "查询其他用户的积分", + + "action-edituserpoints": "编辑用户积分", + "right-edituserpoints": "编辑用户积分", + "isekai-userpoints-point-name-num": "$2 $3 $1", "isekai-userpoints-service-system": "积分系统", diff --git a/includes/Hooks.php b/includes/Hooks.php index 5c2362b..c0ebfb3 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -3,6 +3,8 @@ namespace Isekai\UserPoints; use DatabaseUpdater; +use DateTime; +use DateTimeZone; use MediaWiki\MediaWikiServices; use OutputPage; use Skin; @@ -62,4 +64,10 @@ class Hooks { } } } + + public static function onResourceLoaderGetConfigVars(array &$vars) { + $defaultTimezone = date_default_timezone_get(); + $tzInfo = new DateTimeZone($defaultTimezone); + $vars['wgTimezoneOffsetMinutes'] = $tzInfo->getOffset(new DateTime()) / 60; + } } diff --git a/modules/ext.isekai.userpoints.dailysign.js b/modules/ext.isekai.userpoints.dailysign.js index 0bfef71..0b5121a 100644 --- a/modules/ext.isekai.userpoints.dailysign.js +++ b/modules/ext.isekai.userpoints.dailysign.js @@ -1,25 +1,44 @@ $(function() { if (!mw.user.isAnon()) { - const storeKey = 'isekai-userpoints-dailysign-lastSignDate'; - const lastSignDate = localStorage.getItem(storeKey); - const today = new Date().toLocaleDateString(); + var storeKey = 'isekai-userpoints-dailysign-lastSignDate'; + var lastSignDate = localStorage.getItem(storeKey); + + function getDateWithTimezone(deltaMinutes) { + var date = new Date(); + var offset = date.getTimezoneOffset(); + var offsetMilliSeconds = offset * 60 * 1000; + var deltaMilliSeconds = deltaMinutes * 60 * 1000; + var newDate = new Date(date.getTime() + offsetMilliSeconds + deltaMilliSeconds); + return newDate; + } + + function getDateString(dateObj) { + var year = dateObj.getFullYear(); + var month = dateObj.getMonth() + 1; + var date = dateObj.getDate(); + return year + '-' + month + '-' + date; + } + + var timezoneOffset = mw.config.get('wgTimezoneOffsetMinutes'); + var today = getDateString(getDateWithTimezone(timezoneOffset)); + if (lastSignDate !== today) { - let mwApi = new mw.Api(); + var mwApi = new mw.Api(); mwApi.postWithToken('csrf', { action: 'userdailysign', }).done(function(data) { if (data.userdailysign && data.userdailysign.success) { if (Array.isArray(data.userdailysign.point_delta)) { - const pointDelta = data.userdailysign.point_delta; - let pointMsgList = []; + var pointDelta = data.userdailysign.point_delta; + var pointMsgList = []; pointDelta.forEach(function (pointDeltaInfo) { - let msg = mw.msg('isekai-userpoints-point-name-num', pointDeltaInfo.name, pointDeltaInfo.icon, pointDeltaInfo.points); + var msg = mw.msg('isekai-userpoints-point-name-num', pointDeltaInfo.name, pointDeltaInfo.icon, pointDeltaInfo.points); pointMsgList.push(msg); }); - let separator = mw.msg('comma-separator'); - let pointMsg = pointMsgList.join(separator); + var separator = mw.msg('comma-separator'); + var pointMsg = pointMsgList.join(separator); - let notificationMsg = mw.msg('isekai-userpoints-dailysign-notify-success', pointMsg); + var notificationMsg = mw.msg('isekai-userpoints-dailysign-notify-success', pointMsg); mw.notify('', { title: mw.msg('isekai-userpoints-dailysign-notify-title'), tag: 'isekai-userpoints-dailysign', @@ -27,7 +46,7 @@ $(function() { }); function changeNotifyContent() { - let notifyDom = document.querySelector('#isekai-userpoints-dailysign-notify'); + var notifyDom = document.querySelector('#isekai-userpoints-dailysign-notify'); if (notifyDom) { notifyDom.querySelector('.mw-notification-content').innerHTML = notificationMsg; } else {