Skip to content

V2EX 新话题

通过定时轮训 V2EX 的 RSS,如果有新帖子则通知 Telegram。

来看一下:https://t.me/v2exc

编写自定义通知

提示

这个 Job 用到了环境变量,请先了解环境变量

下面的代码主要是看 main() 方法,XMLParser 是一个类库

javascript
/**
 * RapJob 全局变量 { request, response, job, environment }
 * RapJob.request 包含了请求信息
 * RapJob.response 包含了响应信息
 * RapJob.job 包含了任务信息
 * RapJob.environment 包含了环境变量信息
 *
 * 阅读详细文档: https://www.rapjob.com/guide/policy.html
 *
 * @return 返回 null 或者 false 表示不通知
 */

var XMLParser; (() => { var t = { 696: t => { const e = /^[-+]?0x[a-fA-F0-9]+$/, r = /^([\-\+])?(0*)(\.[0-9]+([eE]\-?[0-9]+)?|[0-9]+(\.[0-9]+([eE]\-?[0-9]+)?)?)$/; !Number.parseInt && window.parseInt && (Number.parseInt = window.parseInt), !Number.parseFloat && window.parseFloat && (Number.parseFloat = window.parseFloat); const i = { hex: !0, leadingZeros: !0, decimalPoint: ".", eNotation: !0 }; t.exports = function (t, n = {}) { if (n = Object.assign({}, i, n), !t || "string" != typeof t) return t; let a = t.trim(); if (void 0 !== n.skipLike && n.skipLike.test(a)) return t; if (n.hex && e.test(a)) return Number.parseInt(a, 16); { const e = r.exec(a); if (e) { const r = e[1], i = e[2]; let o = (s = e[3]) && -1 !== s.indexOf(".") ? ("." === (s = s.replace(/0+$/, "")) ? s = "0" : "." === s[0] ? s = "0" + s : "." === s[s.length - 1] && (s = s.substr(0, s.length - 1)), s) : s; const l = e[4] || e[6]; if (!n.leadingZeros && i.length > 0 && r && "." !== a[2]) return t; if (!n.leadingZeros && i.length > 0 && !r && "." !== a[1]) return t; { const e = Number(a), s = "" + e; return -1 !== s.search(/[eE]/) || l ? n.eNotation ? e : t : -1 !== a.indexOf(".") ? "0" === s && "" === o || s === o || r && s === "-" + o ? e : t : i ? o === s || r + o === s ? e : t : a === s || a === r + s ? e : t } } return t } var s } }, 825: (t, e) => { "use strict"; var r = ":A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD", i = "[" + r + "][" + r + "\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*", n = new RegExp("^" + i + "$"); e.isExist = function (t) { return void 0 !== t }, e.isEmptyObject = function (t) { return 0 === Object.keys(t).length }, e.merge = function (t, e, r) { if (e) for (var i = Object.keys(e), n = i.length, a = 0; a < n; a++)t[i[a]] = "strict" === r ? [e[i[a]]] : e[i[a]] }, e.getValue = function (t) { return e.isExist(t) ? t : "" }, e.isName = function (t) { return !(null == n.exec(t)) }, e.getAllMatches = function (t, e) { for (var r = [], i = e.exec(t); i;) { var n = []; n.startIndex = e.lastIndex - i[0].length; for (var a = i.length, s = 0; s < a; s++)n.push(i[s]); r.push(n), i = e.exec(t) } return r }, e.nameRegexp = i }, 631: (t, e, r) => { "use strict"; var i = r(825), n = { allowBooleanAttributes: !1, unpairedTags: [] }; function a(t) { return " " === t || "\t" === t || "\n" === t || "\r" === t } function s(t, e) { for (var r = e; e < t.length; e++)if ("?" != t[e] && " " != t[e]); else { var i = t.substr(r, e - r); if (e > 5 && "xml" === i) return p("InvalidXml", "XML declaration allowed only at the start of the document.", c(t, e)); if ("?" == t[e] && ">" == t[e + 1]) { e++; break } } return e } function o(t, e) { if (t.length > e + 5 && "-" === t[e + 1] && "-" === t[e + 2]) { for (e += 3; e < t.length; e++)if ("-" === t[e] && "-" === t[e + 1] && ">" === t[e + 2]) { e += 2; break } } else if (t.length > e + 8 && "D" === t[e + 1] && "O" === t[e + 2] && "C" === t[e + 3] && "T" === t[e + 4] && "Y" === t[e + 5] && "P" === t[e + 6] && "E" === t[e + 7]) { var r = 1; for (e += 8; e < t.length; e++)if ("<" === t[e]) r++; else if (">" === t[e] && 0 == --r) break } else if (t.length > e + 9 && "[" === t[e + 1] && "C" === t[e + 2] && "D" === t[e + 3] && "A" === t[e + 4] && "T" === t[e + 5] && "A" === t[e + 6] && "[" === t[e + 7]) for (e += 8; e < t.length; e++)if ("]" === t[e] && "]" === t[e + 1] && ">" === t[e + 2]) { e += 2; break } return e } e.validate = function (t, e) { e = Object.assign({}, n, e); var r, l = [], u = !1, g = !1; "\ufeff" === t[0] && (t = t.substr(1)); for (var v = 0; v < t.length; v++)if ("<" === t[v] && "?" === t[v + 1]) { if ((v = s(t, v += 2)).err) return v } else { if ("<" !== t[v]) { if (a(t[v])) continue; return p("InvalidChar", "char '" + t[v] + "' is not expected.", c(t, v)) } var m = v; if ("!" === t[++v]) { v = o(t, v); continue } var x = !1; "/" === t[v] && (x = !0, v++); for (var b = ""; v < t.length && ">" !== t[v] && " " !== t[v] && "\t" !== t[v] && "\n" !== t[v] && "\r" !== t[v]; v++)b += t[v]; if ("/" === (b = b.trim())[b.length - 1] && (b = b.substring(0, b.length - 1), v--), r = b, !i.isName(r)) return p("InvalidTag", 0 === b.trim().length ? "Invalid space after '<'." : "Tag '" + b + "' is an invalid name.", c(t, v)); var N = f(t, v); if (!1 === N) return p("InvalidAttr", "Attributes for '" + b + "' have open quote.", c(t, v)); var E = N.value; if (v = N.index, "/" === E[E.length - 1]) { var T = v - E.length, w = d(E = E.substring(0, E.length - 1), e); if (!0 !== w) return p(w.err.code, w.err.msg, c(t, T + w.err.line)); u = !0 } else if (x) { if (!N.tagClosed) return p("InvalidTag", "Closing tag '" + b + "' doesn't have proper closing.", c(t, v)); if (E.trim().length > 0) return p("InvalidTag", "Closing tag '" + b + "' can't have attributes or invalid starting.", c(t, m)); if (0 === l.length) return p("InvalidTag", "Closing tag '" + b + "' has not been opened.", c(t, m)); var y = l.pop(); if (b !== y.tagName) { var O = c(t, y.tagStartPos); return p("InvalidTag", "Expected closing tag '" + y.tagName + "' (opened in line " + O.line + ", col " + O.col + ") instead of closing tag '" + b + "'.", c(t, m)) } 0 == l.length && (g = !0) } else { var I = d(E, e); if (!0 !== I) return p(I.err.code, I.err.msg, c(t, v - E.length + I.err.line)); if (!0 === g) return p("InvalidXml", "Multiple possible root nodes found.", c(t, v)); -1 !== e.unpairedTags.indexOf(b) || l.push({ tagName: b, tagStartPos: m }), u = !0 } for (v++; v < t.length; v++)if ("<" === t[v]) { if ("!" === t[v + 1]) { v = o(t, ++v); continue } if ("?" !== t[v + 1]) break; if ((v = s(t, ++v)).err) return v } else if ("&" === t[v]) { var A = h(t, v); if (-1 == A) return p("InvalidChar", "char '&' is not expected.", c(t, v)); v = A } else if (!0 === g && !a(t[v])) return p("InvalidXml", "Extra text at the end", c(t, v)); "<" === t[v] && v-- } return u ? 1 == l.length ? p("InvalidTag", "Unclosed tag '" + l[0].tagName + "'.", c(t, l[0].tagStartPos)) : !(l.length > 0) || p("InvalidXml", "Invalid '" + JSON.stringify(l.map((function (t) { return t.tagName })), null, 4).replace(/\r?\n/g, "") + "' found.", { line: 1, col: 1 }) : p("InvalidXml", "Start tag expected.", 1) }; var l = '"', u = "'"; function f(t, e) { for (var r = "", i = "", n = !1; e < t.length; e++) { if (t[e] === l || t[e] === u) "" === i ? i = t[e] : i !== t[e] || (i = ""); else if (">" === t[e] && "" === i) { n = !0; break } r += t[e] } return "" === i && { value: r, index: e, tagClosed: n } } var g = new RegExp("(\\s*)([^\\s=]+)(\\s*=)?(\\s*(['\"])(([\\s\\S])*?)\\5)?", "g"); function d(t, e) { for (var r = i.getAllMatches(t, g), n = {}, a = 0; a < r.length; a++) { if (0 === r[a][1].length) return p("InvalidAttr", "Attribute '" + r[a][2] + "' has no space in starting.", m(r[a])); if (void 0 !== r[a][3] && void 0 === r[a][4]) return p("InvalidAttr", "Attribute '" + r[a][2] + "' is without value.", m(r[a])); if (void 0 === r[a][3] && !e.allowBooleanAttributes) return p("InvalidAttr", "boolean attribute '" + r[a][2] + "' is not allowed.", m(r[a])); var s = r[a][2]; if (!v(s)) return p("InvalidAttr", "Attribute '" + s + "' is an invalid name.", m(r[a])); if (n.hasOwnProperty(s)) return p("InvalidAttr", "Attribute '" + s + "' is repeated.", m(r[a])); n[s] = 1 } return !0 } function h(t, e) { if (";" === t[++e]) return -1; if ("#" === t[e]) return function (t, e) { var r = /\d/; for ("x" === t[e] && (e++, r = /[\da-fA-F]/); e < t.length; e++) { if (";" === t[e]) return e; if (!t[e].match(r)) break } return -1 }(t, ++e); for (var r = 0; e < t.length; e++, r++)if (!(t[e].match(/\w/) && r < 20)) { if (";" === t[e]) break; return -1 } return e } function p(t, e, r) { return { err: { code: t, msg: e, line: r.line || r, col: r.col } } } function v(t) { return i.isName(t) } function c(t, e) { var r = t.substring(0, e).split(/\r?\n/); return { line: r.length, col: r[r.length - 1].length + 1 } } function m(t) { return t.startIndex + t[1].length } }, 785: (t, e, r) => { var i = r(825); function n(t, e) { for (var r = ""; e < t.length && "'" !== t[e] && '"' !== t[e]; e++)r += t[e]; if (-1 !== (r = r.trim()).indexOf(" ")) throw new Error("External entites are not supported"); for (var i = t[e++], n = ""; e < t.length && t[e] !== i; e++)n += t[e]; return [r, n, e] } function a(t, e) { return "!" === t[e + 1] && "-" === t[e + 2] && "-" === t[e + 3] } function s(t, e) { return "!" === t[e + 1] && "E" === t[e + 2] && "N" === t[e + 3] && "T" === t[e + 4] && "I" === t[e + 5] && "T" === t[e + 6] && "Y" === t[e + 7] } function o(t, e) { return "!" === t[e + 1] && "E" === t[e + 2] && "L" === t[e + 3] && "E" === t[e + 4] && "M" === t[e + 5] && "E" === t[e + 6] && "N" === t[e + 7] && "T" === t[e + 8] } function l(t, e) { return "!" === t[e + 1] && "A" === t[e + 2] && "T" === t[e + 3] && "T" === t[e + 4] && "L" === t[e + 5] && "I" === t[e + 6] && "S" === t[e + 7] && "T" === t[e + 8] } function u(t, e) { return "!" === t[e + 1] && "N" === t[e + 2] && "O" === t[e + 3] && "T" === t[e + 4] && "A" === t[e + 5] && "T" === t[e + 6] && "I" === t[e + 7] && "O" === t[e + 8] && "N" === t[e + 9] } function f(t) { if (i.isName(t)) return t; throw new Error("Invalid entity name " + t) } t.exports = function (t, e) { var r = {}; if ("O" !== t[e + 3] || "C" !== t[e + 4] || "T" !== t[e + 5] || "Y" !== t[e + 6] || "P" !== t[e + 7] || "E" !== t[e + 8]) throw new Error("Invalid Tag instead of DOCTYPE"); e += 9; for (var i = 1, g = !1, d = !1; e < t.length; e++)if ("<" !== t[e] || d) if (">" === t[e]) { if (d ? "-" === t[e - 1] && "-" === t[e - 2] && (d = !1, i--) : i--, 0 === i) break } else "[" === t[e] ? g = !0 : t[e]; else { if (g && s(t, e)) { var h = n(t, (e += 7) + 1); entityName = h[0], val = h[1], e = h[2], -1 === val.indexOf("&") && (r[f(entityName)] = { regx: RegExp("&" + entityName + ";", "g"), val }) } else if (g && o(t, e)) e += 8; else if (g && l(t, e)) e += 8; else if (g && u(t, e)) e += 9; else { if (!a) throw new Error("Invalid DOCTYPE"); d = !0 } i++ } if (0 !== i) throw new Error("Unclosed DOCTYPE"); return { entities: r, i: e } } }, 7: (t, e) => { var r = { preserveOrder: !1, attributeNamePrefix: "@_", attributesGroupName: !1, textNodeName: "#text", ignoreAttributes: !0, removeNSPrefix: !1, allowBooleanAttributes: !1, parseTagValue: !0, parseAttributeValue: !1, trimValues: !0, cdataPropName: !1, numberParseOptions: { hex: !0, leadingZeros: !0, eNotation: !0 }, tagValueProcessor: function (t, e) { return e }, attributeValueProcessor: function (t, e) { return e }, stopNodes: [], alwaysCreateTextNode: !1, isArray: function () { return !1 }, commentPropName: !1, unpairedTags: [], processEntities: !0, htmlEntities: !1, ignoreDeclaration: !1, ignorePiTags: !1, transformTagName: !1, transformAttributeName: !1, updateTag: function (t, e, r) { return t } }; e.buildOptions = function (t) { return Object.assign({}, r, t) }, e.defaultOptions = r }, 731: (t, e, r) => { "use strict"; var i = r(825), n = r(501), a = r(785), s = r(696); function o(t) { for (var e = Object.keys(t), r = 0; r < e.length; r++) { var i = e[r]; this.lastEntities[i] = { regex: new RegExp("&" + i + ";", "g"), val: t[i] } } } function l(t, e, r, i, n, a, s) { if (void 0 !== t && (this.options.trimValues && !i && (t = t.trim()), t.length > 0)) { s || (t = this.replaceEntitiesValue(t)); var o = this.options.tagValueProcessor(e, t, r, n, a); return null == o ? t : typeof o != typeof t || o !== t ? o : this.options.trimValues || t.trim() === t ? N(t, this.options.parseTagValue, this.options.numberParseOptions) : t } } function u(t) { if (this.options.removeNSPrefix) { var e = t.split(":"), r = "/" === t.charAt(0) ? "/" : ""; if ("xmlns" === e[0]) return ""; 2 === e.length && (t = r + e[1]) } return t } var f = new RegExp("([^\\s=]+)\\s*(=\\s*(['\"])([\\s\\S]*?)\\3)?", "gm"); function g(t, e, r) { if (!this.options.ignoreAttributes && "string" == typeof t) { for (var n = i.getAllMatches(t, f), a = n.length, s = {}, o = 0; o < a; o++) { var l = this.resolveNameSpace(n[o][1]), u = n[o][4], g = this.options.attributeNamePrefix + l; if (l.length) if (this.options.transformAttributeName && (g = this.options.transformAttributeName(g)), "__proto__" === g && (g = "#__proto__"), void 0 !== u) { this.options.trimValues && (u = u.trim()), u = this.replaceEntitiesValue(u); var d = this.options.attributeValueProcessor(l, u, e); s[g] = null == d ? u : typeof d != typeof u || d !== u ? d : N(u, this.options.parseAttributeValue, this.options.numberParseOptions) } else this.options.allowBooleanAttributes && (s[g] = !0) } if (!Object.keys(s).length) return; if (this.options.attributesGroupName) { var h = {}; return h[this.options.attributesGroupName] = s, h } return s } } var d = function (t) { t = t.replace(/\r\n?/g, "\n"); for (var e = new n("!xml"), r = e, i = "", s = "", o = 0; o < t.length; o++)if ("<" === t[o]) if ("/" === t[o + 1]) { var l = m(t, ">", o, "Closing Tag is not closed."), u = t.substring(o + 2, l).trim(); if (this.options.removeNSPrefix) { var f = u.indexOf(":"); -1 !== f && (u = u.substr(f + 1)) } this.options.transformTagName && (u = this.options.transformTagName(u)), r && (i = this.saveTextToParentTag(i, r, s)); var g = s.substring(s.lastIndexOf(".") + 1); if (u && -1 !== this.options.unpairedTags.indexOf(u)) throw new Error("Unpaired tag can not be used as closing tag: </" + u + ">"); var d = 0; g && -1 !== this.options.unpairedTags.indexOf(g) ? (d = s.lastIndexOf(".", s.lastIndexOf(".") - 1), this.tagsNodeStack.pop()) : d = s.lastIndexOf("."), s = s.substring(0, d), r = this.tagsNodeStack.pop(), i = "", o = l } else if ("?" === t[o + 1]) { var h = x(t, o, !1, "?>"); if (!h) throw new Error("Pi Tag is not closed."); if (i = this.saveTextToParentTag(i, r, s), this.options.ignoreDeclaration && "?xml" === h.tagName || this.options.ignorePiTags); else { var p = new n(h.tagName); p.add(this.options.textNodeName, ""), h.tagName !== h.tagExp && h.attrExpPresent && (p[":@"] = this.buildAttributesMap(h.tagExp, s, h.tagName)), this.addChild(r, p, s) } o = h.closeIndex + 1 } else if ("!--" === t.substr(o + 1, 3)) { var v = m(t, "--\x3e", o + 4, "Comment is not closed."); if (this.options.commentPropName) { var c, b = t.substring(o + 4, v - 2); i = this.saveTextToParentTag(i, r, s), r.add(this.options.commentPropName, [(c = {}, c[this.options.textNodeName] = b, c)]) } o = v } else if ("!D" === t.substr(o + 1, 2)) { var N = a(t, o); this.docTypeEntities = N.entities, o = N.i } else if ("![" === t.substr(o + 1, 2)) { var E = m(t, "]]>", o, "CDATA is not closed.") - 2, T = t.substring(o + 9, E); i = this.saveTextToParentTag(i, r, s); var w, y = this.parseTextData(T, r.tagname, s, !0, !1, !0, !0); null == y && (y = ""), this.options.cdataPropName ? r.add(this.options.cdataPropName, [(w = {}, w[this.options.textNodeName] = T, w)]) : r.add(this.options.textNodeName, y), o = E + 2 } else { var O = x(t, o, this.options.removeNSPrefix), I = O.tagName, A = O.rawTagName, P = O.tagExp, C = O.attrExpPresent, S = O.closeIndex; this.options.transformTagName && (I = this.options.transformTagName(I)), r && i && "!xml" !== r.tagname && (i = this.saveTextToParentTag(i, r, s, !1)); var k = r; if (k && -1 !== this.options.unpairedTags.indexOf(k.tagname) && (r = this.tagsNodeStack.pop(), s = s.substring(0, s.lastIndexOf("."))), I !== e.tagname && (s += s ? "." + I : I), this.isItStopNode(this.options.stopNodes, s, I)) { var F = ""; if (P.length > 0 && P.lastIndexOf("/") === P.length - 1) "/" === I[I.length - 1] ? (I = I.substr(0, I.length - 1), s = s.substr(0, s.length - 1), P = I) : P = P.substr(0, P.length - 1), o = O.closeIndex; else if (-1 !== this.options.unpairedTags.indexOf(I)) o = O.closeIndex; else { var _ = this.readStopNodeData(t, A, S + 1); if (!_) throw new Error("Unexpected end of " + A); o = _.i, F = _.tagContent } var D = new n(I); I !== P && C && (D[":@"] = this.buildAttributesMap(P, s, I)), F && (F = this.parseTextData(F, I, s, !0, C, !0, !0)), s = s.substr(0, s.lastIndexOf(".")), D.add(this.options.textNodeName, F), this.addChild(r, D, s) } else { if (P.length > 0 && P.lastIndexOf("/") === P.length - 1) { "/" === I[I.length - 1] ? (I = I.substr(0, I.length - 1), s = s.substr(0, s.length - 1), P = I) : P = P.substr(0, P.length - 1), this.options.transformTagName && (I = this.options.transformTagName(I)); var j = new n(I); I !== P && C && (j[":@"] = this.buildAttributesMap(P, s, I)), this.addChild(r, j, s), s = s.substr(0, s.lastIndexOf(".")) } else { var V = new n(I); this.tagsNodeStack.push(r), I !== P && C && (V[":@"] = this.buildAttributesMap(P, s, I)), this.addChild(r, V, s), r = V } i = "", o = S } } else i += t[o]; return e.child }; function h(t, e, r) { var i = this.options.updateTag(e.tagname, r, e[":@"]); !1 === i || ("string" == typeof i ? (e.tagname = i, t.addChild(e)) : t.addChild(e)) } var p = function (t) { if (this.options.processEntities) { for (var e in this.docTypeEntities) { var r = this.docTypeEntities[e]; t = t.replace(r.regx, r.val) } for (var i in this.lastEntities) { var n = this.lastEntities[i]; t = t.replace(n.regex, n.val) } if (this.options.htmlEntities) for (var a in this.htmlEntities) { var s = this.htmlEntities[a]; t = t.replace(s.regex, s.val) } t = t.replace(this.ampEntity.regex, this.ampEntity.val) } return t }; function v(t, e, r, i) { return t && (void 0 === i && (i = 0 === Object.keys(e.child).length), void 0 !== (t = this.parseTextData(t, e.tagname, r, !1, !!e[":@"] && 0 !== Object.keys(e[":@"]).length, i)) && "" !== t && e.add(this.options.textNodeName, t), t = ""), t } function c(t, e, r) { var i = "*." + r; for (var n in t) { var a = t[n]; if (i === a || e === a) return !0 } return !1 } function m(t, e, r, i) { var n = t.indexOf(e, r); if (-1 === n) throw new Error(i); return n + e.length - 1 } function x(t, e, r, i) { void 0 === i && (i = ">"); var n = function (t, e, r) { var i; void 0 === r && (r = ">"); for (var n = "", a = e; a < t.length; a++) { var s = t[a]; if (i) s === i && (i = ""); else if ('"' === s || "'" === s) i = s; else if (s === r[0]) { if (!r[1]) return { data: n, index: a }; if (t[a + 1] === r[1]) return { data: n, index: a } } else "\t" === s && (s = " "); n += s } }(t, e + 1, i); if (n) { var a = n.data, s = n.index, o = a.search(/\s/), l = a, u = !0; -1 !== o && (l = a.substring(0, o), a = a.substring(o + 1).trimStart()); var f = l; if (r) { var g = l.indexOf(":"); -1 !== g && (u = (l = l.substr(g + 1)) !== n.data.substr(g + 1)) } return { tagName: l, tagExp: a, closeIndex: s, attrExpPresent: u, rawTagName: f } } } function b(t, e, r) { for (var i = r, n = 1; r < t.length; r++)if ("<" === t[r]) if ("/" === t[r + 1]) { var a = m(t, ">", r, e + " is not closed"); if (t.substring(r + 2, a).trim() === e && 0 == --n) return { tagContent: t.substring(i, r), i: a }; r = a } else if ("?" === t[r + 1]) r = m(t, "?>", r + 1, "StopNode is not closed."); else if ("!--" === t.substr(r + 1, 3)) r = m(t, "--\x3e", r + 3, "StopNode is not closed."); else if ("![" === t.substr(r + 1, 2)) r = m(t, "]]>", r, "StopNode is not closed.") - 2; else { var s = x(t, r, ">"); s && ((s && s.tagName) === e && "/" !== s.tagExp[s.tagExp.length - 1] && n++, r = s.closeIndex) } } function N(t, e, r) { if (e && "string" == typeof t) { var n = t.trim(); return "true" === n || "false" !== n && s(t, r) } return i.isExist(t) ? t : "" } t.exports = function (t) { this.options = t, this.currentNode = null, this.tagsNodeStack = [], this.docTypeEntities = {}, this.lastEntities = { apos: { regex: /&(apos|#39|#x27);/g, val: "'" }, gt: { regex: /&(gt|#62|#x3E);/g, val: ">" }, lt: { regex: /&(lt|#60|#x3C);/g, val: "<" }, quot: { regex: /&(quot|#34|#x22);/g, val: '"' } }, this.ampEntity = { regex: /&(amp|#38|#x26);/g, val: "&" }, this.htmlEntities = { space: { regex: /&(nbsp|#160);/g, val: " " }, cent: { regex: /&(cent|#162);/g, val: "¢" }, pound: { regex: /&(pound|#163);/g, val: "£" }, yen: { regex: /&(yen|#165);/g, val: "¥" }, euro: { regex: /&(euro|#8364);/g, val: "€" }, copyright: { regex: /&(copy|#169);/g, val: "©" }, reg: { regex: /&(reg|#174);/g, val: "®" }, inr: { regex: /&(inr|#8377);/g, val: "₹" }, num_dec: { regex: /&#([0-9]{1,7});/g, val: function (t, e) { return String.fromCharCode(Number.parseInt(e, 10)) } }, num_hex: { regex: /&#x([0-9a-fA-F]{1,6});/g, val: function (t, e) { return String.fromCharCode(Number.parseInt(e, 16)) } } }, this.addExternalEntities = o, this.parseXml = d, this.parseTextData = l, this.resolveNameSpace = u, this.buildAttributesMap = g, this.isItStopNode = c, this.replaceEntitiesValue = p, this.readStopNodeData = b, this.saveTextToParentTag = v, this.addChild = h } }, 354: (t, e, r) => { var i = r(7).buildOptions, n = r(731), a = r(120).prettify, s = r(631), o = function () { function t(t) { this.externalEntities = {}, this.options = i(t) } var e = t.prototype; return e.parse = function (t, e) { if ("string" == typeof t); else { if (!t.toString) throw new Error("XML data is accepted in String or Bytes[] form."); t = t.toString() } if (e) { !0 === e && (e = {}); var r = s.validate(t, e); if (!0 !== r) throw Error(r.err.msg + ":" + r.err.line + ":" + r.err.col) } var i = new n(this.options); i.addExternalEntities(this.externalEntities); var o = i.parseXml(t); return this.options.preserveOrder || void 0 === o ? o : a(o, this.options) }, e.addEntity = function (t, e) { if (-1 !== e.indexOf("&")) throw new Error("Entity value can't have '&'"); if (-1 !== t.indexOf("&") || -1 !== t.indexOf(";")) throw new Error("An entity must be set without '&' and ';'. Eg. use '#xD' for '&#xD;'"); if ("&" === e) throw new Error("An entity with value '&' is not permitted"); this.externalEntities[t] = e }, t }(); t.exports = o }, 120: (t, e) => { "use strict"; function r(t, e, s) { for (var o, l = {}, u = 0; u < t.length; u++) { var f, g = t[u], d = i(g); if (f = void 0 === s ? d : s + "." + d, d === e.textNodeName) void 0 === o ? o = g[d] : o += "" + g[d]; else { if (void 0 === d) continue; if (g[d]) { var h = r(g[d], e, f), p = a(h, e); g[":@"] ? n(h, g[":@"], f, e) : 1 !== Object.keys(h).length || void 0 === h[e.textNodeName] || e.alwaysCreateTextNode ? 0 === Object.keys(h).length && (e.alwaysCreateTextNode ? h[e.textNodeName] = "" : h = "") : h = h[e.textNodeName], void 0 !== l[d] && l.hasOwnProperty(d) ? (Array.isArray(l[d]) || (l[d] = [l[d]]), l[d].push(h)) : e.isArray(d, f, p) ? l[d] = [h] : l[d] = h } } } return "string" == typeof o ? o.length > 0 && (l[e.textNodeName] = o) : void 0 !== o && (l[e.textNodeName] = o), l } function i(t) { for (var e = Object.keys(t), r = 0; r < e.length; r++) { var i = e[r]; if (":@" !== i) return i } } function n(t, e, r, i) { if (e) for (var n = Object.keys(e), a = n.length, s = 0; s < a; s++) { var o = n[s]; i.isArray(o, r + "." + o, !0, !0) ? t[o] = [e[o]] : t[o] = e[o] } } function a(t, e) { var r = e.textNodeName, i = Object.keys(t).length; return 0 === i || !(1 !== i || !t[r] && "boolean" != typeof t[r] && 0 !== t[r]) } e.prettify = function (t, e) { return r(t, e) } }, 501: t => { "use strict"; var e = function () { function t(t) { this.tagname = t, this.child = [], this[":@"] = {} } var e = t.prototype; return e.add = function (t, e) { var r; "__proto__" === t && (t = "#__proto__"), this.child.push(((r = {})[t] = e, r)) }, e.addChild = function (t) { var e, r; "__proto__" === t.tagname && (t.tagname = "#__proto__"), t[":@"] && Object.keys(t[":@"]).length > 0 ? this.child.push(((e = {})[t.tagname] = t.child, e[":@"] = t[":@"], e)) : this.child.push(((r = {})[t.tagname] = t.child, r)) }, t }(); t.exports = e } }, e = {}, r = function r(i) { var n = e[i]; if (void 0 !== n) return n.exports; var a = e[i] = { exports: {} }; return t[i](a, a.exports, r), a.exports }(354); XMLParser = r })();

function main() {
    const { request, response, job, environment } = RapJob

    // 不通知
    if (response.status !== 200) {
        return false
    }

    // 解析 XML
    const entries = new XMLParser().parse(response.body).feed.entry
    // 获取通知过的索引
    const ids = JSON.parse(environment.getItem('t_list') || '[]')
    const lastDate = parseInt(environment.getItem('lastDate') || '0')

    for (let i = entries.length - 1; i >= 0; i--) {
        const e = entries[i]
        const id = e.id.split('/')[2]
        const link = `https://www.v2ex.com/t/${id}`
        const title = e.title
        const now = new Date(e.published)
        if (now.getTime() < lastDate) {
            continue
        }

        // 日期格式化
        const time = now.getFullYear() + "-" + ((now.getMonth() + 1) < 10 ? '0' + (now.getMonth() + 1) : now.getMonth()) + "-" + now.getDate() + " " + (now.getHours() < 10 ?
            ("0" + now.getHours()) : now.getHours()) + ":" + (now.getMinutes() < 10 ? ("0" + now.getMinutes()) : now.getMinutes()) + ":" + (now.getSeconds() < 10 ? ("0" + now.getSeconds()) : now.getSeconds())

        // 去掉所有 HTML 标签和连续换行
        let content = e.content.replace(/<[^>]*>/g, '\n').replace(/(\s*\r?\n\s*)+/g, "\n")

        // 超出 256 个字符显示 ...
        if (content.length > 256) {
            content = content.substring(0, 256) + " ..."
        }

        const tId = `t_${id}`

        // 判断是否通知过
        if (ids.includes(tId)) {
            continue
        }

        ids.push(tId)

        // 超过了 51 个就删除第一个,因为v2ex的rss每次只返回50个。
        if (ids.length >= 51) {
            ids.shift()
        }

        environment.setItem('lastDate', now.getTime().toString())

        // 索引字段
        environment.setItem('t_list', JSON.stringify(ids))

        return {
            notification: {
                title: `[${job.jobName}] 调用成功`,
                // 消息内容
                content: `<b>V2EX 新话题</b>\n\n<b>${title}</b>\n\n<a href="${e.author.uri}">${e.author.name}</a> | ${time} \n${content} \n<a href='${link}'>More</a>`,
            }
        }
    }

    return false

}