これは@[email protected]による移植版です。
参照: https://gist.github.com/saki-lere/8d128649391d59fb0e4c942c5557328e
Copyright 2022 taiy https://github.com/taiyme
Apache License Version 2.0 https://www.apache.org/licenses/LICENSE-2.0
配布しているプラグインの質問や要望は @[email protected] へお願いします。
設定 > クライアント設定「プラグイン」> プラグインのインストール
免責: プラグインの導入は自己責任でお願いします。
Notes: プラグインの説明 - 配布ファイル。
パクるプラグインを適用すると、ノートメニューに「パクる」が、数字引用プラグインを適用すると、ノートメニューに「数字引用する」が出現します。
数字引用について → 数字引用 - Submarin Wiki
【設定】 以下のオプションを指定します。
auto public home followers有効 無効auto の場合、この指定は無視され、元のノートに追従します。有効 無効auto の場合、この指定は無視され、元のノートに追従します。有効 無効有効 無効有効 無効有効 無効Notes: 数字引用プラグインは、それぞれ「パクる」を「数字引用する」に読み替えます。
【権限】 このプラグインはそれぞれの理由のため権限の許可が必要です。
投稿時刻を表示するプラグインを適用すると、ノートメニューに「投稿時刻を表示」が出現します。
【設定】 以下のオプションを指定します。
YYYY/MM/DD HH:mm:ss.SSS540【権限】 このプラグインは許可の必要な権限はありません。
いまのなしって言ったら直前のノートが削除されるやつ
ここが詳しい → いまのなし | Misskey Plugins & CSS
【設定】 以下のオプションを指定します。
いまのなし有効 無効有効 無効Notes: いまのなしv2.0.0以降、完全一致/部分一致のオプションが独立し、個別で指定できるようになりました。
【権限】 このプラグインはそれぞれの理由のため権限の許可が必要です。
| /// @ 0.12.4 | |
| ### { | |
| name: "パクるプラグイン (AS0.12.4版)" | |
| version: "2.0.0" | |
| author: "@salano_ym (Original: taiy)" | |
| description: "ノートメニューに\"パクる\"を追加します。" | |
| permissions: ["write:notes" "write:drive" "read:drive"] | |
| config: { | |
| visibility: { | |
| type: "string" | |
| label: "公開範囲" | |
| description: "(既定値: auto) 以下から、投稿するノートの公開範囲を指定します。 auto public home followers" | |
| default: "auto" | |
| } | |
| localOnly: { | |
| type: "boolean" | |
| label: "常にローカルでパクる" | |
| description: "(既定値: 無効) 公開範囲が\"auto\"の場合、この指定は無視され、元のノートに追従します。" | |
| default: false | |
| } | |
| channel: { | |
| type: "boolean" | |
| label: "元のチャンネルでパクる" | |
| description: "(既定値: 有効) 公開範囲が\"auto\"の場合、この指定は無視され、元のノートに追従します。" | |
| default: true | |
| } | |
| reply: { | |
| type: "boolean" | |
| label: "返信元でパクる" | |
| description: "(既定値: 有効)" | |
| default: true | |
| } | |
| renote: { | |
| type: "boolean" | |
| label: "引用もパクる" | |
| description: "(既定値: 有効)" | |
| default: true | |
| } | |
| poll: { | |
| type: "boolean" | |
| label: "投票もパクる" | |
| description: "(既定値: 有効)" | |
| default: true | |
| } | |
| files: { | |
| type: "boolean" | |
| label: "添付ファイルもパクる" | |
| description: "(既定値: 有効) ドライブに保存・添付するため、時間を要することがあります。" | |
| default: true | |
| } | |
| } | |
| } | |
| let c = {} | |
| c.visibility = Plugin:config.visibility | |
| if !(["auto" "public" "home" "followers"].incl(c.visibility)) { | |
| c.visibility = "auto" | |
| Mk:dialog("パクるプラグイン" `公開範囲の指定に誤りがあります。{Str:lf}自動で **{c.visibility}** が適用されます。` "warning") | |
| } | |
| c.localOnly = Plugin:config.localOnly | |
| c.channel = Plugin:config.channel | |
| c.reply = Plugin:config.reply | |
| c.renote = Plugin:config.renote | |
| c.poll = Plugin:config.poll | |
| c.files = Plugin:config.files | |
| c.auto = (c.visibility == "auto") | |
| @upload(files callback) { | |
| @at(arr index) { | |
| if (index < 0) return null | |
| if (arr.len <= index) return null | |
| return arr[index] | |
| } | |
| @lastAt(arr) { | |
| return at(arr (arr.len-1)) | |
| } | |
| @fn_upload(file) { | |
| let uuid = Util:uuid() | |
| let uploadObj = { | |
| url: file.url | |
| force: true | |
| isSensitive: file.isSensitive | |
| comment: uuid | |
| } | |
| if (file.marker != null) { | |
| uploadObj.marker = file.marker | |
| } | |
| Mk:api("drive/files/upload-from-url" uploadObj) | |
| return { | |
| uuid: uuid | |
| comment: file.comment | |
| } | |
| } | |
| let latest = lastAt(Mk:api("drive/stream" {limit: 1})) | |
| var latestId = if (latest != null) Obj:get(latest "id") else null | |
| let arr = files.map(@(file){ fn_upload(file) }) | |
| let complete = Core:range(1 arr.len).map(@(v){ null }) | |
| let streamObj = {limit: arr.len} | |
| if (latestId != null) { | |
| streamObj.sinceId = latestId | |
| } | |
| let stop = Async:interval(2000 @() { | |
| let result = Mk:api("drive/stream" streamObj) | |
| if (result != null) { | |
| each let file result { | |
| arr.map(@(obj index) { | |
| if (file.comment == obj.uuid) { | |
| file = Mk:api("drive/files/update" { | |
| fileId: file.id | |
| comment: obj.comment | |
| }) | |
| complete[index] = file | |
| } | |
| }) | |
| latestId = file.id | |
| } | |
| } | |
| if !(complete.incl(null)) { | |
| stop() | |
| callback(complete) | |
| } | |
| } true) | |
| return null | |
| } | |
| @parse(note callback) { | |
| @toBool(value) { | |
| match value { | |
| true => return true | |
| false => return false | |
| "" => return false | |
| null => return false | |
| 0 => return false | |
| } | |
| return true | |
| } | |
| @or(arr) { | |
| var bool = false | |
| arr.find(@(v) { | |
| return if toBool(v) { | |
| bool = true | |
| true | |
| } else false | |
| }) | |
| return bool | |
| } | |
| @and(arr) { | |
| var bool = true | |
| arr.find(@(v) { | |
| return if toBool(v) false else { | |
| bool = false | |
| true | |
| } | |
| }) | |
| return bool | |
| } | |
| let obj = { | |
| text: note.text | |
| cw: note.cw | |
| } | |
| if c.auto { | |
| obj.visibility = note.visibility | |
| if (note.localOnly != null) { | |
| obj.localOnly = note.localOnly | |
| } | |
| if (note.channelId != null) { | |
| obj.channelId = note.channelId | |
| } | |
| } else { | |
| obj.visibility = c.visibility | |
| obj.localOnly = c.localOnly | |
| if and([c.channel note.channelId]) { | |
| obj.channelId = note.channelId | |
| } | |
| } | |
| if and([c.reply note.replyId]) { | |
| obj.replyId = note.replyId | |
| } | |
| if and([c.renote note.renoteId]) { | |
| obj.renoteId = note.renoteId | |
| } | |
| if and([c.poll note.poll]) { | |
| let poll = { | |
| choices: note.poll.choices.map(@(choice) { | |
| return if (Core:type(choice) == "obj") Obj:get(choice "text") else choice | |
| }) | |
| } | |
| if (note.poll.multiple != null) { | |
| poll.multiple = note.poll.multiple | |
| } | |
| if (note.poll.expiresAt != null) { | |
| let diff = (Date:parse(note.poll.expiresAt) - Date:parse(note.createdAt)) | |
| poll.expiredAfter = diff | |
| } | |
| obj.poll = poll | |
| } | |
| if and([c.files note.files.len]) { | |
| upload(note.files @(files) { | |
| obj.fileIds = files.map(@(file) { file.id }) | |
| callback(obj) | |
| }) | |
| } else { | |
| callback(obj) | |
| } | |
| return null | |
| } | |
| Plugin:register_note_action("パクる" @(note) { | |
| parse(note @(parsed) { | |
| Mk:api("notes/create" parsed) | |
| }) | |
| }) |
| /// @ 0.12.4 | |
| ### { | |
| name: "数字引用プラグイン (AS0.12.4版)" | |
| version: "2.0.0" | |
| author: "@salano_ym (Original: taiy)" | |
| description: "ノートメニューに\"数字引用する\"を追加します。" | |
| permissions: ["write:notes" "write:drive" "read:drive"] | |
| config: { | |
| visibility: { | |
| type: "string" | |
| label: "公開範囲" | |
| description: "(既定値: auto) 以下から、投稿するノートの公開範囲を指定します。 auto public home followers" | |
| default: "auto" | |
| } | |
| localOnly: { | |
| type: "boolean" | |
| label: "常にローカルで数字引用する" | |
| description: "(既定値: 無効) 公開範囲が\"auto\"の場合、この指定は無視され、元のノートに追従します。" | |
| default: false | |
| } | |
| channel: { | |
| type: "boolean" | |
| label: "元のチャンネルで数字引用する" | |
| description: "(既定値: 有効) 公開範囲が\"auto\"の場合、この指定は無視され、元のノートに追従します。" | |
| default: true | |
| } | |
| reply: { | |
| type: "boolean" | |
| label: "返信元で数字引用する" | |
| description: "(既定値: 有効)" | |
| default: true | |
| } | |
| renote: { | |
| type: "boolean" | |
| label: "引用も数字引用する" | |
| description: "(既定値: 有効)" | |
| default: true | |
| } | |
| poll: { | |
| type: "boolean" | |
| label: "投票も数字引用する" | |
| description: "(既定値: 有効)" | |
| default: true | |
| } | |
| files: { | |
| type: "boolean" | |
| label: "添付ファイルも数字引用する" | |
| description: "(既定値: 有効) ドライブに保存・添付するため、時間を要することがあります。" | |
| default: true | |
| } | |
| } | |
| } | |
| let c = {} | |
| c.visibility = Plugin:config.visibility | |
| if !(["auto" "public" "home" "followers"].incl(c.visibility)) { | |
| c.visibility = "auto" | |
| Mk:dialog("数字引用プラグイン" `公開範囲の指定に誤りがあります。{Str:lf}自動で **{c.visibility}** が適用されます。` "warning") | |
| } | |
| c.localOnly = Plugin:config.localOnly | |
| c.channel = Plugin:config.channel | |
| c.reply = Plugin:config.reply | |
| c.renote = Plugin:config.renote | |
| c.poll = Plugin:config.poll | |
| c.files = Plugin:config.files | |
| c.auto = (c.visibility == "auto") | |
| @numberquote(text) { | |
| if (text == null) { | |
| text = "" | |
| } | |
| @genArr(size) { | |
| return Core:range(1 size).map(@(v){null}) | |
| } | |
| @lastStr(str len) { | |
| let end = str.len | |
| str.slice((end - len) end) | |
| } | |
| if (lastStr(text 9) == "</center>") { | |
| text = `{text}{Str:lf}` | |
| } | |
| let text_len = text.len | |
| let result = [] | |
| genArr(text_len).find(@(v i) { // i=1..len | |
| i = text_len - i | |
| let s = text.pick(i) | |
| let n = s.to_num() | |
| if (n != null) { | |
| result.unshift(s) | |
| return false | |
| } | |
| if (s == "-") { | |
| if ((text_len-1) != i) { | |
| result.unshift(s) | |
| } | |
| return true | |
| } | |
| return true | |
| }) | |
| var str = result.join("") | |
| let num = if (str != "") str.to_num() else 1 | |
| if (str != "") { | |
| text = text.slice(0 (text_len - result.len)) | |
| } | |
| return `{text}{Core:to_str((num + 1))}` | |
| } | |
| @upload(files callback) { | |
| @at(arr index) { | |
| if (index < 0) return null | |
| if (arr.len <= index) return null | |
| return arr[index] | |
| } | |
| @lastAt(arr) { | |
| return at(arr (arr.len-1)) | |
| } | |
| @fn_upload(file) { | |
| let uuid = Util:uuid() | |
| let uploadObj = { | |
| url: file.url | |
| force: true | |
| isSensitive: file.isSensitive | |
| comment: uuid | |
| } | |
| if (file.marker != null) { | |
| uploadObj.marker = file.marker | |
| } | |
| Mk:api("drive/files/upload-from-url" uploadObj) | |
| return { | |
| uuid: uuid | |
| comment: file.comment | |
| } | |
| } | |
| let latest = lastAt(Mk:api("drive/stream" {limit: 1})) | |
| var latestId = if (latest != null) Obj:get(latest "id") else null | |
| let arr = files.map(@(file){ fn_upload(file) }) | |
| let complete = Core:range(1 arr.len).map(@(v){ null }) | |
| let streamObj = {limit: arr.len} | |
| if (latestId != null) { | |
| streamObj.sinceId = latestId | |
| } | |
| let stop = Async:interval(2000 @() { | |
| let result = Mk:api("drive/stream" streamObj) | |
| if (result != null) { | |
| each let file result { | |
| arr.map(@(obj index) { | |
| if (file.comment == obj.uuid) { | |
| file = Mk:api("drive/files/update" { | |
| fileId: file.id | |
| comment: obj.comment | |
| }) | |
| complete[index] = file | |
| } | |
| }) | |
| latestId = file.id | |
| } | |
| } | |
| if !(complete.incl(null)) { | |
| stop() | |
| callback(complete) | |
| } | |
| } true) | |
| return null | |
| } | |
| @parse(note callback) { | |
| @toBool(value) { | |
| match value { | |
| true => return true | |
| false => return false | |
| "" => return false | |
| null => return false | |
| 0 => return false | |
| } | |
| return true | |
| } | |
| @or(arr) { | |
| var bool = false | |
| arr.find(@(v) { | |
| return if toBool(v) { | |
| bool = true | |
| true | |
| } else false | |
| }) | |
| return bool | |
| } | |
| @and(arr) { | |
| var bool = true | |
| arr.find(@(v) { | |
| return if toBool(v) false else { | |
| bool = false | |
| true | |
| } | |
| }) | |
| return bool | |
| } | |
| let obj = { | |
| text: note.text | |
| cw: note.cw | |
| } | |
| if c.auto { | |
| obj.visibility = note.visibility | |
| if (note.localOnly != null) { | |
| obj.localOnly = note.localOnly | |
| } | |
| if (note.channelId != null) { | |
| obj.channelId = note.channelId | |
| } | |
| } else { | |
| obj.visibility = c.visibility | |
| obj.localOnly = c.localOnly | |
| if and([c.channel note.channelId]) { | |
| obj.channelId = note.channelId | |
| } | |
| } | |
| if and([c.reply note.replyId]) { | |
| obj.replyId = note.replyId | |
| } | |
| if and([c.renote note.renoteId]) { | |
| obj.renoteId = note.renoteId | |
| } | |
| if and([c.poll note.poll]) { | |
| let poll = { | |
| choices: note.poll.choices.map(@(choice) { | |
| return if (Core:type(choice) == "obj") Obj:get(choice "text") else choice | |
| }) | |
| } | |
| if (note.poll.multiple != null) { | |
| poll.multiple = note.poll.multiple | |
| } | |
| if (note.poll.expiresAt != null) { | |
| let diff = (Date:parse(note.poll.expiresAt) - Date:parse(note.createdAt)) | |
| poll.expiredAfter = diff | |
| } | |
| obj.poll = poll | |
| } | |
| if and([c.files note.files.len]) { | |
| upload(note.files @(files) { | |
| obj.fileIds = files.map(@(file) { file.id }) | |
| callback(obj) | |
| }) | |
| } else { | |
| callback(obj) | |
| } | |
| return null | |
| } | |
| Plugin:register_note_action("数字引用する" @(note) { | |
| parse(note @(parsed) { | |
| parsed.text = numberquote(parsed.text) | |
| Mk:api("notes/create" parsed) | |
| }) | |
| }) |
| /// @ 0.12.4 | |
| ### { | |
| name: "投稿時刻を表示するプラグイン (AS0.12.4版)" | |
| version: "1.0.1" | |
| author: "@salano_ym (Original: taiy)" | |
| description: "ノートメニューに\"投稿時刻を表示\"を追加します。" | |
| permissions: [] | |
| config: { | |
| format: { | |
| type: "string" | |
| label: "書式" | |
| description: "(既定値: YYYY/MM/DD HH:mm:ss.SSS) フォーマットを指定します。Day.jsと同一のトークンが利用できます。(一部非対応)" | |
| default: "YYYY/MM/DD HH:mm:ss.SSS" | |
| } | |
| offset: { | |
| type: "number" | |
| label: "時差" | |
| description: "(既定値: 540) 時差を分単位で指定します。日本標準時(JST)は540です。夏時間には非対応です。" | |
| default: 540 | |
| } | |
| } | |
| } | |
| let has_format = (Plugin:config.format == "") || (Plugin:config.format == null) | |
| let c_format = if has_format "YYYY/MM/DD HH:mm:ss.SSS" else Plugin:config.format | |
| let has_offset = (Plugin:config.offset == "") || (Plugin:config.offset == null) | |
| let c_offset = if has_offset 540 else Plugin:config.offset | |
| if has_format { | |
| Mk:dialog("投稿時刻を表示するプラグインより" `書式の指定がありません。{Str:lf}自動で **YYYY/MM/DD HH:mm:ss.SSS** が適用されます。` "warn") | |
| } | |
| if has_offset { | |
| Mk:dialog("投稿時刻を表示するプラグインより" `時差の指定がありません。{Str:lf}自動で **540** が適用されます。` "warn") | |
| } | |
| @format(str tokens) { | |
| @sort(arr mapfn) { | |
| @bubbleSort(arr) { | |
| let length = arr.len | |
| for let i, length { | |
| for let j, (length - i - 1) { | |
| if (Obj:get(arr[j] "index") > Obj:get(arr[j + 1] "index")) { | |
| let tmp = arr[j] | |
| arr[j] = arr[j + 1] | |
| arr[j + 1] = tmp | |
| } | |
| } | |
| } | |
| return arr | |
| } | |
| return bubbleSort(arr.map(mapfn)).map(@(obj) { | |
| return Obj:get(obj "value") | |
| }) | |
| } | |
| @flat(arr) { | |
| @fn_isArr(val) { | |
| return (Core:type(val) == "arr") | |
| } | |
| @fn_flat(arr) { | |
| arr = arr.reduce(@(prev val) { | |
| if !(fn_isArr(val)) { | |
| val = [val] | |
| } | |
| return prev.concat(val) | |
| } []) | |
| if (arr.filter(fn_isArr).len != 0) { | |
| arr = fn_flat(arr) | |
| } | |
| return arr | |
| } | |
| return fn_flat(arr.copy()) | |
| } | |
| @at(arr index) { | |
| if (index < 0) return null | |
| if (arr.len <= index) return null | |
| return arr[index] | |
| } | |
| @startAt(arr) { | |
| return at(arr 0) | |
| } | |
| @lastAt(arr) { | |
| return at(arr (arr.len-1)) | |
| } | |
| @toStr(strArr) { | |
| return flat(strArr).map(@(strObj) { | |
| return Obj:get(strObj "string") | |
| }).join("") | |
| } | |
| tokens = sort(Obj:kvs(tokens) @(arr) { | |
| let token = arr[0] | |
| let value = arr[1] | |
| return { | |
| index: token.len | |
| value: { | |
| token: token | |
| value: value | |
| } | |
| } | |
| }) | |
| tokens.reverse() | |
| str = str.split("").reduce(@(prev cur) { | |
| if (cur == "[") { | |
| prev.push({ | |
| string: "" | |
| replaced: true | |
| }) | |
| } elif (cur == "]") { | |
| prev.push({ | |
| string: "" | |
| replaced: false | |
| }) | |
| } else { | |
| let last = lastAt(prev) | |
| let s = Obj:get(last "string") | |
| Obj:set(last "string" `{s}{cur}`) | |
| } | |
| return prev | |
| } [{ | |
| string: "" | |
| replaced: false | |
| }]) | |
| let result = tokens.reduce(@(strArr tokenObj) { | |
| strArr = flat(strArr) | |
| let t = Obj:get(tokenObj "token") | |
| let v = Obj:get(tokenObj "value") | |
| return strArr.map(@(strObj) { | |
| let s = Obj:get(strObj "string") | |
| let r = Obj:get(strObj "replaced") | |
| if r { | |
| return strObj | |
| } | |
| let a = s.split(t) | |
| return a.reduce(@(prev cur index) { // index=1~len | |
| if (cur != "") { | |
| prev.push({ | |
| string: cur | |
| replaced: false | |
| }) | |
| } | |
| if (a.len != index) { | |
| prev.push({ | |
| string: v | |
| replaced: true | |
| }) | |
| } | |
| return prev | |
| } []) | |
| }) | |
| } str) | |
| return toStr(result) | |
| } | |
| @DateObj(unixtime minuteOffset) { | |
| let YEAR_ONE = 365 | |
| let YEAR_FOUR = 1461 | |
| let YEAR_100 = 36524 | |
| let YEAR_400 = 146097 | |
| let EPOCH_DAY = 719468 | |
| let monthday = [0,31,61,92,122,153,184,214,245,275,306,337] | |
| let result = { | |
| unixtime: unixtime | |
| minuteOffset: minuteOffset | |
| } | |
| unixtime = (unixtime + (minuteOffset * 60000)) | |
| var unixday = Math:floor((unixtime / 86400000)) | |
| var leap = 0 | |
| var year = 0 | |
| var month = null | |
| var day = null | |
| var n = null | |
| var hour = (Math:floor((unixtime / 3600000)) % 24) | |
| var minute = (Math:floor((unixtime / 60000)) % 60) | |
| var second = (Math:floor((unixtime / 1000)) % 60) | |
| var millisecond = (unixtime % 1000) | |
| if (unixtime < 0) { | |
| hour = (hour + 24) | |
| minute = (minute + 60) | |
| second = (second + 60) | |
| millisecond = (millisecond + 1000) | |
| } | |
| let weekday = ((Math:floor((unixday + 4)) % 7) + 1) | |
| if (weekday < 0) { | |
| weekday = (weekday + 7) | |
| } | |
| unixday = (unixday + EPOCH_DAY) | |
| year = (year + (400 * Math:floor((unixday / YEAR_400)))) | |
| unixday = (unixday % YEAR_400) | |
| n = Math:floor((unixday / YEAR_100)) | |
| year = (year + (n * 100)) | |
| unixday = (unixday % YEAR_100) | |
| if (n == 4) { | |
| leap = 1 | |
| } else { | |
| year = (year + (4 * Math:floor((unixday / YEAR_FOUR)))) | |
| unixday = (unixday % YEAR_FOUR) | |
| n = Math:floor((unixday / YEAR_ONE)) | |
| year = (year + n) | |
| unixday = (unixday % YEAR_ONE) | |
| if (n == 4) { | |
| leap = 1 | |
| } | |
| } | |
| if (leap != 0) { | |
| month = 2 | |
| day = 29 | |
| } else { | |
| month = Math:floor((((unixday * 5) + 2) / 153)) | |
| day = ((unixday - monthday[month]) + 1) | |
| month = (month + 3) | |
| if (month > 12) { | |
| year = (year + 1) | |
| month = (month - 12) | |
| } | |
| } | |
| result.year = year | |
| result.month = month | |
| result.day = day | |
| result.hour = hour | |
| result.minute = minute | |
| result.second = second | |
| result.millisecond = millisecond | |
| result.weekday = weekday | |
| return result | |
| } | |
| @DateTokenObj(unixtime minuteOffset) { | |
| let d = DateObj(unixtime minuteOffset) | |
| let result = {} | |
| @zeroPadding(n length) { | |
| let num = n | |
| let abs = Math:abs(num) | |
| let str = abs.to_str() | |
| let sign = if (num < 0) "-" else "" | |
| let len = Math:max(0 (length - str.len)) | |
| let pstr = if (len == 0) "" else Core:range(1 len).map(@(v){"0"}).join("") | |
| return `{sign}{pstr}{str}` | |
| } | |
| let z = zeroPadding | |
| result.YY = z((d.year % 100) 2) | |
| result.YYYY = z(d.year 4) | |
| result.M = z(d.month 1) | |
| result.MM = z(d.month 2) | |
| result.D = z(d.day 1) | |
| result.DD = z(d.day 2) | |
| result.d = z(d.weekday 1) | |
| var hour12 = (d.hour % 12) | |
| if (hour12 == 0) { | |
| hour12 = 12 | |
| } | |
| result.H = z(d.hour 1) | |
| result.HH = z(d.hour 2) | |
| result.h = z(hour12 1) | |
| result.hh = z(hour12 2) | |
| result.m = z(d.minute 1) | |
| result.mm = z(d.minute 2) | |
| result.s = z(d.second 1) | |
| result.ss = z(d.second 2) | |
| result.SSS = z(d.millisecond 3) | |
| let tzoffset = Math:abs(d.minuteOffset) | |
| let tzsign = if (d.minuteOffset < 0) "-" else "+" | |
| let tzhour = z((Math:floor((tzoffset / 60)) % 60) 2) | |
| let tzminute = z((tzoffset % 60) 2) | |
| result.Z = `{tzsign}{tzhour}:{tzminute}` | |
| result.ZZ = `{tzsign}{tzhour}{tzminute}` | |
| result.A = if (d.hour < 12) "AM" else "PM" | |
| result.a = if (d.hour < 12) "am" else "pm" | |
| @indicator(n) { | |
| let i = Math:abs(n) | |
| let cent = (i % 100) | |
| if Core:and((10 <= cent) (cent <= 20)) return `{n}th` | |
| let dec = (i % 10) | |
| match dec { | |
| 1 => return `{n}st` | |
| 2 => return `{n}nd` | |
| 3 => return `{n}rd` | |
| } | |
| return `{n}th` | |
| } | |
| result.Do = indicator(d.day) | |
| var hour24 = (d.hour % 24) | |
| if (hour24 == 0) { | |
| hour24 = 24 | |
| } | |
| result.k = z(hour24 1) | |
| result.kk = z(hour24 2) | |
| result.X = `{Math:floor((d.unixtime / 1000))}` | |
| result.x = `{d.unixtime}` | |
| return result | |
| } | |
| @dateformat(fmt str offset) { | |
| return format(fmt DateTokenObj(Date:parse(str) offset)) | |
| } | |
| Plugin:register_note_action("投稿時刻を表示" @(note) { | |
| let formated = dateformat(c_format note.createdAt c_offset) | |
| Mk:dialog("投稿時刻" formated "info") | |
| }) | |
| Plugin:register_user_action("作成日時を表示" @(user) { | |
| let formated = dateformat(c_format user.createdAt c_offset) | |
| Mk:dialog("作成日時" formated "info") | |
| }) |
| /// @ 0.12.4 | |
| ### { | |
| name: "いまのなし (AS0.12.4版)" | |
| version: "2.0.1" | |
| author: "@salano_ym (Original: taiy)" | |
| description: "消せ消せ消せ消せ消せ https://gist.github.com/saki-lere/8d128649391d59fb0e4c942c5557328e" | |
| permissions: ["write:notes"] | |
| config: { | |
| exactWords: { | |
| type: "string" | |
| label: "単語(完全一致)" | |
| description: "(既定値: いまのなし) カンマ区切りで複数指定します。nyaizeは以下の言語に対応しています。 ja-JP, en-US" | |
| default: "いまのなし" | |
| } | |
| partialWords: { | |
| type: "string" | |
| label: "単語(部分一致)" | |
| description: "(既定値なし) \"単語(完全一致)\" の説明を参照します。" | |
| default: "" | |
| } | |
| exactAlert: { | |
| type: "boolean" | |
| label: "削除を確認する(完全一致)" | |
| description: "(既定値: 有効)" | |
| default: true | |
| } | |
| partialAlert: { | |
| type: "boolean" | |
| label: "削除を確認する(部分一致)" | |
| description: "(既定値: 有効)" | |
| default: true | |
| } | |
| } | |
| } | |
| let limitObj = { | |
| userId: Obj:get(Mk:api("i" {}) "id") | |
| limit: 1 | |
| } | |
| :: Arr { | |
| @at(arr index) { | |
| return if ((index < 0) || (arr.len <= index)) null | |
| else arr[index] | |
| } | |
| @lastAt(arr) { | |
| return Arr:at(arr (arr.len-1)) | |
| } | |
| } | |
| :: Str { | |
| @replaceFn(text old fn) { | |
| let textU = text.upper() | |
| let oldU = old.upper() | |
| let oldLen = old.len | |
| let textUArr = textU.split(oldU) | |
| let obj = textUArr.reduce(@(prev cur i) { | |
| if (i != 1) { | |
| let begin = prev.len | |
| let end = (begin + oldLen) | |
| prev.len = end | |
| prev.arr.push({ | |
| isMatched: true | |
| string: text.slice(begin end) | |
| }) | |
| } | |
| let begin = prev.len | |
| let end = (begin + cur.len) | |
| prev.len = end | |
| prev.arr.push({ | |
| isMatched: false | |
| string: text.slice(begin end) | |
| }) | |
| return prev | |
| } {len: 1, arr: []}) | |
| return obj.arr.map(@(o) { | |
| return if (o.string == "") "" | |
| elif o.isMatched fn(o.string) | |
| else o.string | |
| }).join("") | |
| } | |
| @nyaize(text) { | |
| text = text.replace("な" "にゃ").replace("ナ" "ニャ").replace("ナ" "ニャ") | |
| text = Str:replaceFn(text "na" @(old) { | |
| let x = if (old.slice(2 3) == "A") "YA" else "ya" | |
| return `{old.slice(1 2)}{x}` | |
| }) | |
| text = Str:replaceFn(text "morning" @(old) { | |
| let x = if (old.slice(5 8) == "ING") "YAN" else "yan" | |
| return `{old.slice(1 5)}{x}` | |
| }) | |
| text = Str:replaceFn(text "everyone" @(old) { | |
| let x = if (old.slice(6 9) == "ONE") "NYAN" else "nyan" | |
| return `{old.slice(1 6)}{x}` | |
| }) | |
| return text | |
| } | |
| @quote(text) { | |
| return `> {text.split(Str:lf).join(`{Str:lf}> `)}` | |
| } | |
| } | |
| let c = {} | |
| c.exactAlert = Plugin:config.exactAlert | |
| c.exactWords = Plugin:config.exactWords | |
| if (c.exactWords == "") { | |
| c.exactWords = "いまのなし" | |
| } | |
| let exactMatches = c.exactWords.split(",").map(@(m) { m.trim() }).filter(@(m) {(m != "")}) | |
| exactMatches.map(@(m) { | |
| exactMatches.unshift(Str:nyaize(m)) | |
| }) | |
| c.partialAlert = Plugin:config.partialAlert | |
| c.partialWords = Plugin:config.partialWords | |
| if (c.partialWords == "") { | |
| c.partialWords = "" | |
| } | |
| let partialMatches = c.partialWords.split(",").map(@(m) { m.trim() }).filter(@(m) {(m != "")}) | |
| partialMatches.map(@(m) { | |
| partialMatches.unshift(Str:nyaize(m)) | |
| }) | |
| let queue = [] | |
| Async:interval(1000 @() { | |
| let obj = queue.shift() | |
| if (obj == null) return null | |
| let alert = match obj.type { | |
| 1 => c.exactAlert | |
| 2 => c.partialAlert | |
| 3 => (c.exactAlert || c.partialAlert) | |
| } | |
| let note = obj.note | |
| let flag = if alert Mk:confirm("" `このノートを削除しますか?{Str:lf}{Str:quote(note.text)}` "warning") else true | |
| if flag { | |
| Mk:api("notes/delete" { | |
| noteId: note.id | |
| }) | |
| } | |
| }) | |
| @judge(text) { | |
| var exact = false | |
| var partial = false | |
| exactMatches.find(@(m) { | |
| exact = (text == m) | |
| return exact | |
| }) | |
| partialMatches.find(@(m) { | |
| partial = text.incl(m) | |
| return partial | |
| }) | |
| return {exact: exact, partial: partial} | |
| } | |
| Plugin:register_note_post_interruptor(@(note) { | |
| let text = Obj:get(note "text") | |
| let judge = judge(text) | |
| let type = if (judge.exact && judge.partial) 3 | |
| elif judge.exact 1 | |
| elif judge.partial 2 | |
| else 0 | |
| if (type != 0) { | |
| queue.push({ | |
| type: type, | |
| note: Arr:lastAt(Mk:api("users/notes" limitObj)) | |
| }) | |
| } | |
| return note | |
| }) |
| /// @ 0.12.4 | |
| ### { | |
| name: "NSFWプラグイン (AS0.12.4版)" | |
| version: "0.0.0 (tester)" | |
| author: "@salano_ym (Original: taiy)" | |
| description: "https://gist.github.com/saki-lere/8d128649391d59fb0e4c942c5557328e" | |
| permissions: [] | |
| config: { | |
| nsfw: { | |
| type: "boolean" | |
| label: "強制NSFW" | |
| description: "(既定値: 有効)" | |
| default: true | |
| } | |
| cw: { | |
| type: "boolean" | |
| label: "強制CW" | |
| description: "(既定値: 無効)" | |
| default: false | |
| } | |
| } | |
| } | |
| @modNSFW(note) { | |
| if (note.files != null) { | |
| note.files.map(@(file) { | |
| file.isSensitive = true | |
| }) | |
| } | |
| return note | |
| } | |
| @modCW(note) { | |
| if (note.cw == null) { | |
| note.cw = "" | |
| } | |
| return note | |
| } | |
| Plugin:register_note_view_interruptor(@(note) { | |
| if Plugin:config.nsfw { | |
| modNSFW(note) | |
| } | |
| if Plugin:config.cw { | |
| modCW(note) | |
| } | |
| return note | |
| }) |