Fix blur effect, implement skeleton loading, and add Duolingo JWT friend streak feature

This commit is contained in:
User
2026-03-12 15:53:23 +08:00
parent 5c0abfd5da
commit 3eaef5b06a
6 changed files with 4835 additions and 3754 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -161,6 +161,55 @@
<?php wp_footer(); ?>
<!-- 长文章 backdrop-filter 模糊层 JS -->
<script>
(function() {
function initPostFullBlur() {
var card = document.querySelector('article.post.post-full.card');
if (!card) return;
// 创建模糊覆盖层
var overlay = document.createElement('div');
overlay.className = 'post-full-blur-overlay';
card.insertBefore(overlay, card.firstChild);
var ticking = false;
function updateOverlay() {
var cardRect = card.getBoundingClientRect();
// 覆盖层比视口高 800px上下各多 400px充分溢出窗口避免穿帮
// offset = 卡片顶部到视口顶部的距离 - 400px 上方余量
var offset = Math.max(0, -cardRect.top - 400);
// 限制偏移量不超过卡片高度减去覆盖层高度
var overlayHeight = window.innerHeight + 800;
var maxOffset = card.offsetHeight - overlayHeight;
if (maxOffset > 0) {
offset = Math.min(offset, maxOffset);
}
overlay.style.transform = 'translateY(' + offset + 'px)';
ticking = false;
}
function onScroll() {
if (!ticking) {
ticking = true;
requestAnimationFrame(updateOverlay);
}
}
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', onScroll, { passive: true });
// 初始定位
updateOverlay();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initPostFullBlur);
} else {
initPostFullBlur();
}
})();
</script>
</body>

View File

@@ -5270,15 +5270,31 @@ function argon_get_duolingo_data() {
}
}
$url = 'https://www.duolingo.com/2017-06-30/users?username=' . urlencode($username) . '&fields=streak,streakData%7BcurrentStreak,previousStreak%7D%7D';
$url = 'https://www.duolingo.com/2017-06-30/users?username=' . urlencode($username) . '&fields=id,streak,streakData%7BcurrentStreak,previousStreak%7D,quests,friendStreaks,friendships,following';
$headers = array(
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'
);
$jwt = get_option('argon_duolingo_jwt', '');
if (!empty($jwt)) {
$headers['Authorization'] = 'Bearer ' . $jwt;
}
$response = wp_remote_get($url, array(
'timeout' => 10,
'headers' => array(
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
)
'timeout' => 15,
'headers' => $headers
));
// 如果返回了诸如 401/403 错误JWT 过期或无效),尝试移除 JWT 重新获取,确保基础连胜可见
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) !== 200 && !empty($jwt)) {
unset($headers['Authorization']);
$response = wp_remote_get($url, array(
'timeout' => 15,
'headers' => $headers
));
$jwt = ''; // 标记为无效,后续不请求私有好友连胜数据
}
if (is_wp_error($response)) {
// 请求失败时返回旧缓存(如果有)
return $cached !== false ? $cached : false;
@@ -5305,10 +5321,48 @@ function argon_get_duolingo_data() {
$end_date = isset($user['streakData']['currentStreak']['endDate']) ? $user['streakData']['currentStreak']['endDate'] : '';
$is_today_done = ($end_date === $today);
// 尝试解析好友连胜 (Friend Streak)
$friend_streak = 0;
// 只有在 JWT 有效时才继续拉取私密接口
if (!empty($jwt)) {
// 请求额外的好友连胜数据
$user_id = isset($user['id']) ? $user['id'] : 0;
if ($user_id) {
$friend_url = 'https://www.duolingo.com/2017-06-30/friends/users/' . $user_id . '?fields=friendStreaks';
$friend_resp = wp_remote_get($friend_url, array('timeout' => 15, 'headers' => $headers));
if (!is_wp_error($friend_resp)) {
$friend_body = wp_remote_retrieve_body($friend_resp);
$friend_data = json_decode($friend_body, true);
// 写出诊断日志以便开发者后续找字段
$debug_info = array('user_api' => $user, 'friend_api' => $friend_data);
file_put_contents(get_template_directory() . '/duolingo_debug.json', json_encode($debug_info, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
// 暂时尝试猜测常用名字
if (isset($user['friendStreaks'])) {
// 如果是在 user 对象里
foreach ((array)$user['friendStreaks'] as $fs) {
if (isset($fs['streakLength']) && $fs['streakLength'] > $friend_streak) $friend_streak = $fs['streakLength'];
if (isset($fs['length']) && $fs['length'] > $friend_streak) $friend_streak = $fs['length'];
}
}
if (isset($friend_data['friendStreaks'])) {
// 如果是在 friends 对象里
foreach ((array)$friend_data['friendStreaks'] as $fs) {
if (isset($fs['streakLength']) && $fs['streakLength'] > $friend_streak) $friend_streak = $fs['streakLength'];
if (isset($fs['length']) && $fs['length'] > $friend_streak) $friend_streak = $fs['length'];
}
}
}
}
}
$result = array(
'streak' => $streak,
'today' => $is_today_done,
'date' => $today
'date' => $today,
'friend_streak' => $friend_streak > 0 ? $friend_streak : ''
);
// 如果今日已完成缓存到明天0点否则缓存15分钟

View File

@@ -1529,6 +1529,14 @@ function themeoptions_page(){
</tr>
<tr>
<th><label>Duolingo JWT Token</label></th>
<td>
<input name="argon_duolingo_jwt" type="text" class="regular-text" value="<?php echo get_option('argon_duolingo_jwt', ''); ?>" placeholder="<?php _e('手动填入抓包得到的 jwt', 'argon');?>">
<p class="description"><?php _e('在浏览器登录多邻国网页版后,按 F12 并在 Application -> Cookies 里找到 jwt 并填入,用于自动获取友情连胜数据', 'argon');?></p>
</td>
</tr>
<tr><th class="subtitle"><h2 id="section-announcement"><?php _e('博客公告', 'argon');?></h2></th></tr>
<tr>
@@ -6882,6 +6890,14 @@ function argon_update_themeoptions(){
argon_update_option('argon_show_duolingo_streak');
argon_update_option('argon_duolingo_username');
if (isset($_POST['argon_duolingo_jwt'])) {
argon_update_option('argon_duolingo_jwt');
}
delete_option('argon_duolingo_friend_streak');
// Clean up old transient and options
delete_option('argon_duolingo_email');
delete_transient('argon_duo_login_error');
argon_update_option('argon_banner_title');

View File

@@ -564,16 +564,17 @@ $author_desc = get_option('argon_sidebar_author_description');
var headIndexInstance = $(document).data('headIndex');
// 添加额外的滚动监听,确保目录跟随
var scrollTimer = null;
var scrollTicking = false;
$(window).on('scroll.desktopCatalog', function() {
if (scrollTimer) {
clearTimeout(scrollTimer);
}
scrollTimer = setTimeout(function() {
if (!scrollTicking) {
scrollTicking = true;
requestAnimationFrame(function() {
if (headIndexInstance && typeof headIndexInstance.updateCurrent === 'function') {
headIndexInstance.updateCurrent();
}
}, 100);
scrollTicking = false;
});
}
});
// PJAX 后重新绑定
@@ -663,8 +664,10 @@ $author_desc = get_option('argon_sidebar_author_description');
$duo_data = argon_get_duolingo_data();
if ($duo_data !== false) :
$is_today_done = isset($duo_data['today']) && $duo_data['today'];
$duo_friend_streak = isset($duo_data['friend_streak']) ? $duo_data['friend_streak'] : '';
$tooltip_attr = !empty($duo_friend_streak) ? ' data-toggle="tooltip" data-placement="bottom" title="' . esc_attr(sprintf(__('友情连胜: %s天', 'argon'), $duo_friend_streak)) . '"' : '';
?>
<span class="duolingo-streak<?php echo $is_today_done ? '' : ' not-done'; ?>">
<span class="duolingo-streak<?php echo $is_today_done ? '' : ' not-done'; ?>"<?php echo $tooltip_attr; ?>>
<img src="<?php echo get_template_directory_uri(); ?>/assets/icons/duolingo-streak<?php echo $is_today_done ? '' : '-empty'; ?>.svg" class="duolingo-flame" alt="streak">
<?php echo $duo_data['streak']; ?>
</span>

1701
style.css

File diff suppressed because it is too large Load Diff