├── .gitattributes ├── .github └── workflows │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── icon.png ├── index.html ├── index.ts └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build plugin 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - '*' # Push events to matching any tag format, i.e. 1.0, 20.15.10 8 | 9 | env: 10 | PLUGIN_NAME: logseq-twitter-extractor 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Use Node.js 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: '16.x' # You might need to adjust this value to your own version 22 | - name: Build 23 | id: build 24 | env: 25 | BEARERTOKEN: ${{ secrets.BEARERTOKEN }} 26 | run: | 27 | npm i && npm run build 28 | mkdir ${{ env.PLUGIN_NAME }} 29 | cp README.md package.json icon.png ${{ env.PLUGIN_NAME }} 30 | mv dist ${{ env.PLUGIN_NAME }} 31 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 32 | ls 33 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)" 34 | - name: Create Release 35 | uses: ncipollo/release-action@v1 36 | id: create_release 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | VERSION: ${{ github.ref }} 40 | with: 41 | allowUpdates: true 42 | draft: false 43 | prerelease: false 44 | 45 | - name: Upload zip file 46 | id: upload_zip 47 | uses: actions/upload-release-asset@v1 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | with: 51 | upload_url: ${{ steps.create_release.outputs.upload_url }} 52 | asset_path: ./${{ env.PLUGIN_NAME }}.zip 53 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip 54 | asset_content_type: application/zip 55 | 56 | - name: Upload package.json 57 | id: upload_metadata 58 | uses: actions/upload-release-asset@v1 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | with: 62 | upload_url: ${{ steps.create_release.outputs.upload_url }} 63 | asset_path: ./package.json 64 | asset_name: package.json 65 | asset_content_type: application/json 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | .pnpm-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Microbundle cache 59 | .rpt2_cache/ 60 | .rts2_cache_cjs/ 61 | .rts2_cache_es/ 62 | .rts2_cache_umd/ 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | .env.production 77 | 78 | # parcel-bundler cache (https://parceljs.org/) 79 | .cache 80 | .parcel-cache 81 | 82 | # Next.js build output 83 | .next 84 | out 85 | 86 | # Nuxt.js build / generate output 87 | .nuxt 88 | dist 89 | 90 | # Gatsby files 91 | .cache/ 92 | # Comment in the public line in if your project uses Gatsby and not Next.js 93 | # https://nextjs.org/blog/next-9-1#public-directory-support 94 | # public 95 | 96 | # vuepress build output 97 | .vuepress/dist 98 | 99 | # Serverless directories 100 | .serverless/ 101 | 102 | # FuseBox cache 103 | .fusebox/ 104 | 105 | # DynamoDB Local files 106 | .dynamodb/ 107 | 108 | # TernJS port file 109 | .tern-port 110 | 111 | # Stores VSCode versions used for testing VSCode extensions 112 | .vscode-test 113 | 114 | # yarn v2 115 | .yarn/cache 116 | .yarn/unplugged 117 | .yarn/build-state.yml 118 | .yarn/install-state.gz 119 | .pnp.* 120 | yarn.lock 121 | package-lock.json 122 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 sawhney17 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Logseq Twitter Extractor Plugin 2 | >If this plugin helps you, I'd really appreciate your support. You can [buy me a coffee here. ](https://www.buymeacoffee.com/sawhney17) 3 | A quick utility to parse a twitter link into a post with a user specified template. Inspired by the Roam Smartblock for twitter extraction. 4 | ![Screen Recording 2022-03-27 at 5 12 12 PM](https://user-images.githubusercontent.com/80150109/160285175-a94d4431-8eaa-4ced-aec9-65adad85e211.gif) 5 | 6 | ## Instructions 7 | 1. Navigate to a block with the twitter URL 8 | 2. Use the slash command `/Parse Twitter URL` 9 | 3. Enjoy :) 10 | 11 | ## Configuration 12 | 1. In plugin settings, you can configure custom templates 13 | 2. Use {URL}, {Name}, {Username}, {Date}, and {Tweet} as placeholders in the template which will be auto replaced 14 | 3. Template 1 is the block that the logseq block with URL will be replaced into 15 | 4. Template 2 is an optional block that will be indented under the first block 16 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sawhney17/logseq-twitter-extractor/723be46c9a21796c420eb191e2da14278f4c71a9/icon.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | logseq-custom-workflows 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import "@logseq/libs"; 2 | import { SettingSchemaDesc } from "@logseq/libs/dist/LSPlugin.user"; 3 | import axios from "axios"; 4 | import 'logseq-dateutils' 5 | import { getDateForPage, getDateForPageWithoutBrackets } from "logseq-dateutils"; 6 | 7 | let BEARERTOKEN = process.env.BEARERTOKEN 8 | const defaultKey = "mod+shift+x" 9 | const baseURL = "https://api.twitter.com/2/tweets/"; 10 | const tweetRegex = /https:\/\/twitter.com.\S*/g 11 | 12 | let settings: SettingSchemaDesc[] = [{ 13 | key: "InsertionTemplateForBlock1", 14 | type: "string", 15 | title: "Insertion template for block 1", 16 | description: "Enter your desired template for the parent block, created by default for every return value of the query. Options are URL, Name, Username, Date, and Tweet", 17 | default: "{TWEET} {Date}, [[{Name}]]" 18 | }, 19 | { 20 | key: "InsertionTemplateForBlock2", 21 | type: "string", 22 | title: "Insertion template for block 2", 23 | description: "Enter your desired template for the child block, created by default only if template is present, for every return value of the query. Options are URL, Name, Username, Date, and Tweet", 24 | default: "" 25 | }, 26 | { //add option for keyboard shortcut 27 | key: "KeyboardShortcut", 28 | type: "string", 29 | title: "Keyboard Shortcut", 30 | description: `Enter your desired keyboard shortcut for the command, default (${defaultKey})`, 31 | default: defaultKey 32 | } 33 | ] 34 | 35 | async function formatDate(dateText) { 36 | var dateObject = new Date(dateText) 37 | 38 | const dateFormat = (await logseq.App.getUserConfigs()).preferredDateFormat 39 | console.log(dateObject) 40 | console.log(dateFormat) 41 | const date = getDateForPageWithoutBrackets(dateObject, dateFormat) 42 | console.log(date) 43 | return date 44 | } 45 | logseq.useSettingsSchema(settings); 46 | 47 | async function templateBlocks(data, template, url) { 48 | var finalString = template 49 | console.log(data) 50 | let replacements = { 51 | "URL": url, 52 | "Date": await formatDate(data.data[0].created_at), 53 | "Tweet": data.data[0].text, 54 | "Name": data["includes"]["users"][0]["name"], 55 | "Username": data["includes"]["users"][0]["username"], 56 | } 57 | for (let key in replacements) { 58 | let regexString = new RegExp(`{${key}}`, "gi") 59 | finalString = finalString.replaceAll(regexString, replacements[key]) 60 | } 61 | return finalString 62 | } 63 | async function parseTweet(id, uuid, url) { 64 | axios.get(`https://api.twitter.com/2/tweets?ids=${id}&tweet.fields=created_at&expansions=author_id&user.fields=created_at`, { 65 | 'headers': { 66 | "Authorization": `Bearer ${BEARERTOKEN}` 67 | } 68 | }).then(async (result) => { 69 | logseq.Editor.updateBlock(uuid, await templateBlocks(result.data, logseq.settings.InsertionTemplateForBlock1, url)) 70 | if (logseq.settings.InsertionTemplateForBlock2 != "") { 71 | logseq.Editor.insertBlock(uuid, await templateBlocks(result.data, logseq.settings.InsertionTemplateForBlock2, url), { sibling: false }) 72 | } 73 | }) 74 | } 75 | async function detectURL(e) { 76 | try { 77 | let url = (await logseq.Editor.getBlock(e.uuid)).content.match(tweetRegex) 78 | let id = url[0].split("/")[5].split("?")[0] 79 | parseTweet(id, e.uuid, url) 80 | } 81 | catch { 82 | logseq.App.showMsg( 83 | "Error: No URL detected", 84 | ) 85 | } 86 | } 87 | 88 | const main = async () => { 89 | console.log("plugin loaded"); 90 | logseq.Editor.registerSlashCommand("Parse Twitter URL", async (e) => { 91 | detectURL(e); 92 | }); 93 | 94 | const registerKeyXtract = () => logseq.App.registerCommandPalette({ 95 | key: "ParseTwitter", 96 | label: "Parse Twitter URL(s)", 97 | keybinding: { 98 | mode: "global", 99 | binding: logseq.settings.KeyboardShortcut.toLowerCase() 100 | }, 101 | }, (e) => { 102 | if (e.uuid != null) { 103 | detectURL(e); 104 | } 105 | else { 106 | logseq.Editor.getSelectedBlocks().then((blocks) => { 107 | for (const x in blocks){ 108 | detectURL(blocks[x]) 109 | } 110 | } 111 | ) 112 | } 113 | }) 114 | 115 | logseq.onSettingsChanged((_updated) => { 116 | logseq.App.unregister_plugin_simple_command(`${logseq.baseInfo.id}/KeyboardShortcut`) 117 | registerKeyXtract() 118 | }); 119 | }; 120 | 121 | logseq.ready(main).catch(console.error); 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logseq-twitter-extractor", 3 | "version": "1.0.15", 4 | "description": "Twitter Extractor", 5 | "main": "dist/index.html", 6 | "targets": { 7 | "main": false 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "build": "parcel build --no-source-maps index.html --public-url ./" 12 | }, 13 | "keywords": [], 14 | "author": "Aryan Sawhney", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@logseq/libs": "0.0.1-alpha.35", 18 | "axios": "^0.26.1", 19 | "logseq-dateutils": "^0.0.20", 20 | "twitter": "^1.7.1", 21 | "v2-twitter": "^1.0.23" 22 | }, 23 | "logseq": { 24 | "id": "logseq-twitter-extractor", 25 | "title": "logseq-twitter-extractor", 26 | "icon": "./icon.png" 27 | }, 28 | "devDependencies": { 29 | "assert": "^2.0.0", 30 | "browserify-zlib": "^0.2.0", 31 | "buffer": "^6.0.3", 32 | "crypto-browserify": "^3.12.0", 33 | "events": "^3.3.0", 34 | "https-browserify": "^1.0.0", 35 | "parcel": "^2.0.0", 36 | "path-browserify": "^1.0.1", 37 | "querystring-es3": "^0.2.1", 38 | "stream-browserify": "^3.0.0", 39 | "stream-http": "^3.2.0", 40 | "url": "^0.11.0" 41 | } 42 | } 43 | --------------------------------------------------------------------------------