完成任务系统

master
落雨楓 10 months ago
parent d39a4861c4
commit 50090fcf5b

@ -0,0 +1,2 @@
<?php
$specialPageAliases = [];

@ -0,0 +1,90 @@
{
"name": "IsekaiUserPointsQuests",
"namemsg": "isekai-userpoints-quests",
"author": "Hyperzlib",
"version": "1.0.0",
"url": "https://git.isekai.cn/Isekai-Project/mediawiki-extension-IsekaiUserPoints",
"descriptionmsg": "isekai-userpoints-quests-desc",
"license-name": "MIT",
"type": "api",
"requires": {
"MediaWiki": ">= 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
}

@ -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": "创建了页面"
}

@ -0,0 +1,32 @@
<?php
namespace Isekai\UserPoints\Quests\Api;
use ApiBase;
use Isekai\UserPoints\Quests\QuestsUtils;
use MediaWiki\Session\SessionManager;
class ApiUserQuestsGetNotification extends ApiBase {
public function __construct( $main, $method ) {
parent::__construct( $main->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;
}
}

@ -0,0 +1,79 @@
<?php
namespace Isekai\UserPoints\Quests;
use MediaWiki\MediaWikiServices;
use MediaWiki\Session\SessionManager;
use OutputPage;
class Hooks {
/**
* Implements LoadExtensionSchemaUpdates hook.
*
* @param \DatabaseUpdater $updater
*/
public static function onLoadExtensionSchemaUpdates($updater) {
$dir = dirname(__DIR__) . '/sql/';
$type = $updater->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);
}
}
}

@ -0,0 +1,152 @@
<?php
namespace Isekai\UserPoints\Quests;
use MediaWiki\MediaWikiServices;
use User;
use Isekai\UserPoints\Service\IsekaiUserPointsFactory;
use Isekai\UserPoints\Utils;
use MediaWiki\Session\SessionManager;
class QuestsUtils {
public const SESSION_KEY_QUEST_COMPLETE_NOTIFICATION = 'IsekaiQuests::questCompleteNotification';
public static function getUserQuestCompleteNum(User $user, string $questType, string $periodTime) {
$dbr = MediaWikiServices::getInstance()->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);
}
}

@ -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);
}
});
}
});

@ -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`);

@ -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);

@ -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"

@ -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": "积分系统",

@ -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;
}
}

@ -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 {

Loading…
Cancel
Save