码桶

发现社区成员的开源项目

ForumSyncWorker.kt4.3 KB
package com.dalao.forum

import android.content.Context
import android.webkit.CookieManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URL

/**
 * 后台轮询:复用 WebView 登录 Cookie 请求论坛接口,比对新帖/评论/私信数量,增加则弹通知。
 *
 * ============ 需要你填的地方 ============
 * STATUS_ENDPOINT:填 dalao.net 返回未读计数的接口地址(需登录后可访问)。
 * 期望返回 JSON,形如:{"new_posts":3,"new_comments":1,"new_messages":2}
 * 若字段名不同,改 parseCounts() 里的取值键名即可。
 * 未配置(留空)时 Worker 安全空转,不会崩溃。
 * =======================================
 */
class ForumSyncWorker(
    context: Context,
    params: WorkerParameters
) : Worker(context, params) {

    companion object {
        private const val STATUS_ENDPOINT = "" // 例如 "https://www.dalao.net/api/notifications"

        private const val PREFS = "forum_sync_prefs"
        private const val KEY_POSTS = "last_posts"
        private const val KEY_COMMENTS = "last_comments"
        private const val KEY_MESSAGES = "last_messages"

        private const val NOTIF_POSTS = 2001
        private const val NOTIF_COMMENTS = 2002
        private const val NOTIF_MESSAGES = 2003
    }

    override fun doWork(): Result {
        if (STATUS_ENDPOINT.isBlank()) return Result.success() // 未配置接口:空转

        val body = fetch(STATUS_ENDPOINT) ?: return Result.retry()
        val counts = parseCounts(body) ?: return Result.success()

        val prefs = applicationContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
        val ctx = applicationContext

        checkAndNotify(
            ctx, NotificationHelper.CHANNEL_POSTS, NOTIF_POSTS,
            prefs, KEY_POSTS, counts.posts,
            ctx.getString(R.string.channel_posts_name),
            ctx.getString(R.string.notif_new_posts, counts.posts)
        )
        checkAndNotify(
            ctx, NotificationHelper.CHANNEL_COMMENTS, NOTIF_COMMENTS,
            prefs, KEY_COMMENTS, counts.comments,
            ctx.getString(R.string.channel_comments_name),
            ctx.getString(R.string.notif_new_comments, counts.comments)
        )
        checkAndNotify(
            ctx, NotificationHelper.CHANNEL_MESSAGES, NOTIF_MESSAGES,
            prefs, KEY_MESSAGES, counts.messages,
            ctx.getString(R.string.channel_messages_name),
            ctx.getString(R.string.notif_new_messages, counts.messages)
        )
        return Result.success()
    }

    private data class Counts(val posts: Int, val comments: Int, val messages: Int)

    private fun parseCounts(body: String): Counts? = try {
        val json = JSONObject(body)
        Counts(
            posts = json.optInt("new_posts", 0),
            comments = json.optInt("new_comments", 0),
            messages = json.optInt("new_messages", 0)
        )
    } catch (e: Exception) {
        null
    }

    private fun checkAndNotify(
        ctx: Context, channel: String, notifId: Int,
        prefs: android.content.SharedPreferences, key: String,
        current: Int, title: String, content: String
    ) {
        val last = prefs.getInt(key, 0)
        if (current > last && current > 0) {
            NotificationHelper.notify(ctx, channel, notifId, title, content)
        }
        prefs.edit().putInt(key, current).apply()
    }

    private fun fetch(endpoint: String): String? {
        var conn: HttpURLConnection? = null
        return try {
            conn = (URL(endpoint).openConnection() as HttpURLConnection).apply {
                requestMethod = "GET"
                connectTimeout = 15000
                readTimeout = 15000
                // 复用 WebView 登录态:带上该域名的 Cookie
                val cookie = CookieManager.getInstance().getCookie(endpoint)
                if (!cookie.isNullOrBlank()) setRequestProperty("Cookie", cookie)
                setRequestProperty("User-Agent", "DomainForumApp")
            }
            if (conn.responseCode == 200) {
                conn.inputStream.bufferedReader().use { it.readText() }
            } else {
                null
            }
        } catch (e: Exception) {
            null
        } finally {
            conn?.disconnect()
        }
    }
}