HARPA.AI
LIBRARYUSE CASESGUIDESAI COMMANDSBLOG

๐Ÿ’ฌย ย YouTube Comment Agent v1.2

Scrapes videos from YouTube on a given topic and leaves summary or custom comments. #youtube

Created by Mei-Ling Zhang
Updated on Nov 11, 08:16
Installed 51 times
RUNS JS CODE

How to Use

IMPORT COMMAND

Content

- label: DEFAULT SETTINGS
  type: group
  steps:
    - condition: '{{g.commentsConfig}} = '
      type: calc
      param: g.commentsConfig
      value:
        format: NESTED
        sources:
          - https://www.youtube.com/results?search_query=harpa+ai&sp=CAASAhAB
          - >-
            https://www.youtube.com/results?search_query=harpa+ai+chrome+extension
        scrape: 'no'
        comment: 'yes'
        minViews: 5000
        style: >-
          Reply with a comment thanking for the video topics and suggest using
          HARPA AI for video summaries.
        mute: 'no'
      func: set
      format: json
    - param: commentsCount
      format: number
      value: '0'
      type: calc
      func: set
    - condition: '{{g.commentsDelay}} ='
      param: g.commentsDelay
      value: '120000'
      type: calc
      func: set
      format: number
    - condition: '{{g.commentsPerRun}} ='
      param: g.commentsPerRun
      value: '50'
      type: calc
      func: set
      format: number
- type: say
  message: >-
    ๐Ÿ’ก This command searches for YouTube video links and leaves comments or
    summaries of the selected type. 


    You can use this Command as a basis for generating and posting your own
    comments.


    ๐Ÿ“ฃ For safety, we recommend not leaving more than 100 comments per day and
    setting up a delay of at least 1 minute after each video.
- label: CURRENT FREQUENCY
  type: ask
  param: type
  message: |-
    **Current comments frequency:**

    **Comments per run:** {{g.commentsPerRun}}

    **Delay in ms:** {{g.commentsDelay}}

    ๐Ÿ’ก 1 min = 60 sec = 60000 ms
  options:
    - label: ๐Ÿ’ฌ COMMENTS
      value: comments
    - label: โ™ป๏ธ VIDEO SUMMARY
      value: summary
    - label: โš™๏ธ FREQUENCY
      value: frequency
    - label: โ›”๏ธ DEL. VIDEOS LIST
      value: clear
  default: ''
  vision:
    enabled: false
    mode: area
    send: true
    hint: ''
  optionsInvalid: false
- condition: '{{type}} = comments'
  label: COMMENTS SETTING
  steps:
    - func: clone
      to: config
      type: calc
      from: g.commentsConfig
    - label: APPLY COMMENT SETTINGS
      func: extract-json
      type: calc
      to: config
      param: config
      index: ''
    - label: CURRENT COMMENT SETTINGS
      message: |-
        ## Current settings:

        **Comment style:** {{config.style}}

        **Sources:** {{config.sources}}

        **Min. video views:** {{config.minViews}}
      type: say
    - param: settings
      options:
        - label: โœ… START
          value: start
        - label: ๐Ÿ”„ RESTORE TO DEFAULT
          value: default
        - label: โš™๏ธ STYLE
          value: style
        - label: โš™๏ธ SOURCES
          value: sources
        - label: โš™๏ธ MIN. VIEWS
          value: views
      vision:
        enabled: false
        mode: area
        hint: ''
        send: true
      type: ask
      message: ''
      default: ''
      optionsInvalid: false
    - condition: '{{settings}} = start'
      label: START
      steps:
        - type: jump
          to: SCRAPE OR NOT
      type: group
    - condition: '{{settings}} = default'
      label: DEFAULT
      steps:
        - value:
            sources:
              - >-
                https://www.youtube.com/results?search_query=harpa+ai&sp=CAASAhAB
              - >-
                https://www.youtube.com/results?search_query=harpa+ai+chrome+extension
            format: NESTED
            scrape: 'no'
            comment: 'yes'
            minViews: 5000
            mute: 'no'
          type: calc
          param: config
          func: set
          format: json
        - message: โœ… Default settings restored.
          type: say
        - type: jump
          to: APPLY COMMENT SETTINGS
      type: group
    - condition: '{{settings}} = style'
      label: STYLE
      steps:
        - param: config.style
          message: >-
            How to reply? Type instructions or pick an option. I will try to
            mimic your reply tone if set in the App Settings.
          options:
            - label: ๐Ÿ‘ AGREE
              value: Reply with agreement about the video content.
            - label: ๐Ÿ‘Ž DISAGREE
              value: Reply with disagreement about the video content.
            - label: ๐Ÿ™Œ ACKNOWLEDGE
              value: Acknowledge the information presented in the video.
            - label: โ“ QUESTION
              value: Ask a relavant question about the video.
            - label: ๐Ÿค” CLARIFY
              value: Request to clarify something from the video.
            - label: ๐Ÿคทโ€๏ธ ASK FOR INFO
              value: Request more information about the video content.
            - label: ๐Ÿง SUGGEST
              value: Provide a suggestion related to the video.
            - label: ๐Ÿ’ฏ PRAISE
              value: Praise the video creator or content.
            - label: ๐Ÿค— EMPATHY
              value: Empathize with the information presented in the video.
            - label: ๐Ÿคž SUPPORT
              value: Support the message or cause presented in the video.
            - label: ๐Ÿ’ช ENCOURAGE
              value: Encourage the video creator or people featured in the video.
            - label: ๐Ÿคจ CRITICIZE
              value: Criticize the video content or presentation.
            - label: ๐Ÿ™ APOLOGY
              value: Apologize in relation to the video content (if applicable).
            - label: ๐Ÿ’ก IDEA
              value: Come up with a relevant idea inspired by the video.
            - label: ๐Ÿ˜‚ JOKE
              value: Write a relevant joke related to the video content.
            - value: $custom
          vision:
            enabled: false
            mode: area
            hint: ''
            send: true
          type: ask
          default: ''
          optionsInvalid: false
        - type: jump
          to: APPLY COMMENT SETTINGS
      type: group
    - condition: '{{settings}} = sources'
      label: SOURCES
      steps:
        - param: urls
          message: >-
            Please paste a list YouTube search links, comma or space separated,
            e.g.:


            ```

            https://www.youtube.com/results?search_query=harpa+ai+chrome+extension,
            https://www.youtube.com/results?search_query=harpa+ai&sp=CAASAhAB

            ```


            You can use YouTube's search filters and then copy the website
            address that includes those filter settings.
          options: null
          vision:
            enabled: false
            mode: area
            hint: ''
            send: true
          type: ask
          default: ''
          optionsInvalid: false
        - type: js
          code: |-
            let links = [];
            const linkRegex = /(https?:\/\/\S+?)(?=[,;\n\s\]]|$)/gi;

            try {
              // any data to string
              const urlString = String(urls);
              // split string
              const urlParts = urlString.split(/[,;\n\s]+/);
              urlParts.forEach(part => {
                const matchedLinks = part.match(linkRegex) || [];
                links = links.concat(matchedLinks);
              });

              // delete "
              links = links.map(link => link.replace(/^"|"$/g, ''));

              // delete duplicates
              links = [...new Set(links.filter(Boolean))];

            } catch (error) {
                return false;
            }

            return links;
          param: config.sources
          timeout: 15000
          args: urls
          silent: true
        - type: jump
          to: APPLY COMMENT SETTINGS
      type: group
    - condition: '{{settings}} = views'
      label: VIEWS
      steps:
        - param: config.minViews
          message: >-
            Pick an option or type a number without any additional notes or
            symbols, for example: 15000
          options:
            - label: 1k
              value: 1000
            - label: 5k
              value: 5000
            - label: 10k
              value: 10000
            - label: 25k
              value: 25000
            - label: 50k
              value: 50000
            - $custom
          vision:
            enabled: false
            mode: area
            hint: ''
            send: true
          type: ask
          default: ''
          optionsInvalid: false
        - type: jump
          to: APPLY COMMENT SETTINGS
      type: group
  type: group
- condition: '{{type}} = summary'
  label: SUMMARY SETTING
  steps:
    - type: calc
      func: clone
      from: g.commentsConfig
      to: config
    - label: APPLY CURRENT SETTINGS
      type: calc
      func: extract-json
      to: config
      param: config
      index: ''
    - label: CURRENT SETTINGS
      message: |-
        ## Current settings:

        **Format:** {{config.format}}

        **Sources:** {{config.sources}}

        **Min. video views:** {{config.minViews}}
      type: say
    - options:
        - label: โœ… START
          value: start
        - label: ๐Ÿ”„ RESTORE TO DEFAULT
          value: default
        - label: โš™๏ธ FORMAT
          value: format
        - label: โš™๏ธ SOURCES
          value: sources
        - label: โš™๏ธ MIN. VIEWS
          value: views
      vision:
        enabled: false
        mode: area
        hint: ''
        send: true
      type: ask
      param: settings
      message: ''
      default: ''
      optionsInvalid: false
    - steps:
        - type: jump
          to: SCRAPE OR NOT
      condition: '{{settings}} = start'
      label: START
      type: group
    - steps:
        - value:
            sources:
              - >-
                https://www.youtube.com/results?search_query=harpa+ai&sp=CAASAhAB
              - >-
                https://www.youtube.com/results?search_query=harpa+ai+chrome+extension
            format: NESTED
            scrape: 'no'
            comment: 'yes'
            minViews: 5000
            mute: 'no'
          type: calc
          param: config
          func: set
          format: json
        - type: say
          message: โœ… Default settings restored.
        - type: jump
          to: CURRENT SETTINGS
      condition: '{{settings}} = default'
      label: DEFAULT
      type: group
    - condition: '{{settings}} = format'
      label: FORMAT
      steps:
        - param: config.format
          options:
            - label: ๐Ÿ“„ REPORT
              value: REPORT
            - label: โšก๏ธ TL;DR
              value: TL;DR
            - label: ๐Ÿ’ฌ TEXT
              value: TEXT
            - label: ๐Ÿ” EXECUTIVE
              value: EXECUTIVE
            - label: ๐Ÿค— EMOJI LIST
              value: EMOJI
            - label: ๐Ÿฆ TWEET
              value: TWEET
            - label: ๐Ÿ•ต FACT CHECK
              value: FACT CHECK
          vision:
            enabled: false
            mode: area
            hint: ''
            send: true
          type: ask
          message: ''
          default: ''
          optionsInvalid: false
        - type: jump
          to: CURRENT SETTINGS
      type: group
    - steps:
        - vision:
            enabled: false
            mode: area
            hint: ''
            send: true
          type: ask
          param: urls
          message: >-
            Please paste a list YouTube search links, comma or space separated,
            e.g.:


            ```

            https://www.youtube.com/results?search_query=harpa+ai+chrome+extension,
            https://www.youtube.com/results?search_query=harpa+ai&sp=CAASAhAB

            ```


            You can use YouTube's search filters and then copy the website
            address that includes those filter settings.
          options: null
          default: ''
          optionsInvalid: false
        - type: js
          args: urls
          code: |-
            let links = [];
            const linkRegex = /(https?:\/\/\S+?)(?=[,;\n\s\]]|$)/gi;

            try {
              // any data to string
              const urlString = String(urls);
              // split string
              const urlParts = urlString.split(/[,;\n\s]+/);
              urlParts.forEach(part => {
                const matchedLinks = part.match(linkRegex) || [];
                links = links.concat(matchedLinks);
              });

              // delete "
              links = links.map(link => link.replace(/^"|"$/g, ''));

              // delete duplicates
              links = [...new Set(links.filter(Boolean))];

            } catch (error) {
                return false;
            }

            return links;
          param: config.sources
          timeout: 15000
          silent: true
        - type: jump
          to: CURRENT SETTINGS
      condition: '{{settings}} = sources'
      label: SOURCES
      type: group
    - steps:
        - options:
            - label: 1k
              value: 1000
            - label: 5k
              value: 5000
            - label: 10k
              value: 10000
            - label: 25k
              value: 25000
            - label: 50k
              value: 50000
            - $custom
          vision:
            enabled: false
            mode: area
            hint: ''
            send: true
          type: ask
          param: config.minViews
          message: >-
            Pick an option or type a number without any additional notes or
            symbols, for example: 15000
          default: ''
          optionsInvalid: false
        - type: jump
          to: APPLY CURRENT SETTINGS
      condition: '{{settings}} = views'
      label: VIEWS
      type: group
  type: group
- condition: '{{type}} = frequency'
  label: FREQUENCY SETTING
  steps:
    - message: 'Comments per run: '
      vision:
        enabled: false
        mode: area
        hint: ''
        send: true
      type: ask
      param: g.commentsPerRun
      options: null
      default: ''
    - message: 'Delay in ms:'
      options:
        - label: 30 sec
          value: 30000
        - label: 1 min
          value: 60000
        - label: 2 min
          value: 120000
        - label: 5 min
          value: 300000
        - label: 10 min
          value: 600000
        - label: 30 min
          value: 1800000
      vision:
        enabled: false
        mode: area
        hint: ''
        send: true
      type: ask
      param: g.commentsDelay
      default: ''
      optionsInvalid: false
    - message: โœ… Frequency adjusted.
      type: say
    - type: jump
      to: CURRENT FREQUENCY
  type: group
- condition: '{{type}} = clear'
  label: CLEAR LIST
  steps:
    - func: delete
      param: g.videos
      type: calc
    - message: 'โœ… List deleted. '
      type: say
    - type: jump
      to: CURRENT FREQUENCY
  type: group
- type: calc
  func: clone
  from: config
  to: g.commentsConfig
- param: config.scrape
  message: Scrape new videos or use the ones that haven't been processed yet?
  options:
    - label: โœ… SCRAPE
      value: 'yes'
    - label: โ›” DON'T SCRAPE
      value: 'no'
  type: ask
  default: ''
  optionsInvalid: false
  label: SCRAPE OR NOT
- condition: '{{config.scrape}} = yes'
  label: SCRAPE VIDEOS
  type: loop
  steps:
    - type: navigate
      url: '{{item}}'
      silent: true
      waitForIdle: false
    - type: wait
      text: Home
      for: text-to-appear
      timeout: 15000
      silent: true
    - code: |-
        await $harpa.scroller.scroll()
        await $harpa.scroller.scroll()
      type: js
      args: ''
      param: ''
      silent: true
    - code: |-
        return Array
            .from(document.querySelectorAll('#thumbnail'))
            .filter(t => t.getAttribute('href'))
            .map(t => ({
                p: t.parentNode.parentNode,
                url: t.getAttribute('href'),
                duration: t.innerText,
            }))
            .map(t => ({
                ...t,
                title: t.p.querySelector('#title-wrapper')?.innerText,
                views: Array.from(t.p.querySelectorAll('.inline-metadata-item'))?.[0]?.innerText,
                when: Array.from(t.p.querySelectorAll('.inline-metadata-item'))?.[1]?.innerText,
                p: undefined,
            }))    
            .filter(t => t.when)
      param: videos
      type: js
      args: ''
      silent: true
    - func: list-merge
      by: url
      type: calc
      to: g.videos
      listA: g.videos
      listB: videos
    - message: >-
        Parsed {{videos.length}} videos from page and merged to
        {{g.videos.length}} videos
      type: say
  list: config.sources
- condition: '{{config.comment}} = yes'
  label: FILTER VIDEOS
  steps:
    - type: calc
      func: clone
      from: g.videos
      to: videos
    - func: list-filter
      match: '{ "status": null }'
      matched: retain
      type: calc
      list: videos
    - code: |-
        function customSort(a, b) {
          const aMinutes = convertToMinutes(a.when);
          const bMinutes = convertToMinutes(b.when);

          const aViews = extractViews(a.views)
          const bViews = extractViews(b.views)

          const aValue = aViews / aMinutes
          const bValue = bViews / bMinutes

          // return bValue - aValue;
          return aMinutes - bMinutes;
        }

        function convertToMinutes(when) {
          const match = when.match(/(\d+)\s*(\w+)\s+ago/);
          if (match) {
            const value = parseInt(match[1]);
            const unit = match[2].toLowerCase();

            // Convert different time units to minutes
            switch (unit) {
              case 'minute':
              case 'minutes':
                return value;
              case 'hour':
              case 'hours':
                return value * 60;
              case 'day':
              case 'days':
                return value * 60 * 24;
              case 'week':
              case 'weeks':
                return value * 60 * 24 * 7;
              case 'month':
              case 'months':
                return value * 60 * 24 * 30;
              case 'year':
              case 'years':
                return value * 60 * 24 * 365;
              default:
                return 0;
            }
          }
          return 0;
        }

        function extractViews(views) {
          const match = views.match(/([\d.]+)\s*(K|M)?\s+views/i);
          if (match) {
            const value = parseFloat(match[1]);
            const unit = match[2] ? match[2].toLowerCase() : '';

            // Convert views to a common unit (e.g., thousands or millions)
            switch (unit) {
              case 'k':
                return value * 1000;
              case 'm':
                return value * 1000000;
              default:
                return value;
            }
          }
          return 0;
        }

        function removeDuplicates(videos) {
          const seen = {}

          return videos.filter(video => {
            if (seen[video.title] && seen[video.title].status)
              return false

            if (seen[video.title] && video.status) {
              seen[video.title] = video
              return true
            }

            if (!seen[video.title]) {
              seen[video.title] = video
              return true
            }

            return false
          })
        }

        return removeDuplicates(args.videos)
          .filter(v => (extractViews(v.views) > args.config.minViews))
          .sort(customSort)
      args: videos, config
      type: js
      param: videos
      silent: true
    - message: Found {{videos.length}} videos to process
      type: say
  type: group
- label: SUMMARY COMMENTS
  steps:
    - label: CHECK IF PROCESSED
      steps:
        - args: item, g
          code: >-
            const urlFragment = item.url.split('?')[1].split('&')[0]; 

            return g.videos.some(video => video.url.includes(urlFragment) &&
            video.status === "done");
          param: processed
          type: js
          timeout: 15000
          silent: true
        - condition: '{{processed}} = true'
          to: END
          type: jump
      type: group
    - message: '๐Ÿ“น  Processing video: [{{item.title}}]({{item.url}})'
      type: say
    - label: TO VIDEO
      steps:
        - url: '{{item.url}}'
          type: navigate
          waitForIdle: false
          silent: true
        - text: Share
          type: wait
          for: text-to-appear
          timeout: 15000
          silent: true
        - code: |-
            const video = document.querySelector('video')
            if (video && args.config.mute === 'yes') {
              video.muted = true
            }

            return $harpa.scroller.scroll({ y: 800 })
          type: js
          args: config
          param: ''
          silent: true
        - for: 2s
          type: wait
          silent: true
        - code: 'return $harpa.scroller.scroll({ y: 0 })'
          type: js
          args: ''
          param: ''
          silent: true
      type: group
    - label: HAS COMMENT
      steps:
        - type: extract
          param: entirePage
          selectorType: ai
          selector: null
          item: null
          default: ''
          silent: true
        - regex: /Key Takeaways for quick navigation/gmi
          func: match
          type: calc
          to: match
          param: entirePage
        - condition: '{{match.length}} > 0'
          func: list-update
          match: '{ "url": "{{item.url}}" }'
          prop: '{ "status": "done" }'
          type: calc
          list: g.videos
        - message: Video has already been commented.
          condition: '{{match.length}} > 0'
          type: say
        - condition: '{{match.length}} > 0'
          type: jump
          to: END
      type: group
    - label: COMMENT OFF
      steps:
        - type: extract
          selectorType: ai
          selector: null
          item: null
          param: entirePage
          default: ''
          silent: true
        - regex: /Comments are turned off/gmi
          type: calc
          func: match
          to: match
          param: entirePage
        - condition: '{{match.length}} > 0'
          type: calc
          func: list-update
          list: g.videos
          match: '{ "url": "{{item.url}}" }'
          prop: '{ "status": "done" }'
        - message: Video has comments turned off.
          condition: '{{match.length}} > 0'
          type: say
        - condition: '{{match.length}} > 0'
          type: jump
          to: END
      type: group
    - condition: '{{transcript}} ='
      label: NO TRANSCRIPT
      steps:
        - prop: '{ "status": "no-transcript" }'
          type: calc
          func: list-update
          list: g.videos
          match: '{ "url": "{{item.url}}" }'
        - message: Video has no transcript.
          type: say
        - type: jump
          to: END
      type: group
    - condition: '{{p1}} = yes'
      label: DETECT LANGUAGE (OPTIONAL)
      steps:
        - type: gpt
          prompt: >-
            You goal is to detect language of the given video transcript:


            {{transcript}}


            Please only output the main transcript language (one word) and
            nothing else, for example: "English", "Spanish", "Russian". If not
            sure about the language, output single word "English".


            Language:
          param: language
          silent: true
        - message: 'Video language: {{language}}'
          type: say
        - condition: >
            {{language}} =~
            ^(?!.*\b(English|Spanish|French|Italian|Portuguese)\b)(Vietnamese|Arabic|Japanese|Hindi|Persian|Korean|Urdu|Chinese|.{12,})
          label: SKIP LANG
          steps:
            - message: SKIP {{language}}
              type: say
            - type: jump
              to: END
          type: group
      type: group
    - label: COMMENT
      steps:
        - type: calc
          func: list-update
          list: g.videos
          match: '{ "url": "{{item.url}}" }'
          prop: '{ "status": "done" }'
        - type: command
          name: YouTube video summary
          inputs:
            - '{{config.format}}'
            - 'YES'
        - value: English
          format: text
          type: calc
          func: set
          param: language
        - type: js
          code: 'return $harpa.scroller.scroll({ y: 0 })'
          args: ''
          param: ''
          silent: true
        - func: increment
          delta: 1
          type: calc
          param: commentsCount
        - condition: '{{g.commentsPerRun}} = {{commentsCount}}'
          label: END TASK
          steps:
            - message: >
                โœ… **Task completed! {{commentsCount}} comments generated and
                posted.**
              type: say
            - type: stop
          type: group
        - for: custom-delay
          delay: '{{g.commentsDelay}}'
          type: wait
          silent: false
      type: group
    - label: END
      type: say
      message: ''
  condition: '{{type}} = summary'
  type: loop
  list: videos
- label: REGULAR COMMENTS
  steps:
    - steps:
        - type: js
          args: item, g
          code: >-
            const urlFragment = item.url.split('?')[1].split('&')[0]; 

            return g.videos.some(video => video.url.includes(urlFragment) &&
            video.status === "done");
          param: processed
          timeout: 15000
          silent: true
        - condition: '{{processed}} = true'
          type: jump
          to: END
      label: CHECK IF PROCESSED
      type: group
    - type: say
      message: '๐Ÿ“น  Processing video: [{{item.title}}]({{item.url}})'
    - steps:
        - type: navigate
          url: '{{item.url}}'
          waitForIdle: false
          silent: true
        - type: wait
          text: Share
          for: text-to-appear
          timeout: 15000
          silent: true
        - type: js
          code: |-
            const video = document.querySelector('video')
            if (video && args.config.mute === 'yes') {
              video.muted = true
            }

            return $harpa.scroller.scroll({ y: 800 })
          args: config
          param: ''
          silent: true
        - type: wait
          for: 2s
          silent: true
        - type: js
          code: 'return $harpa.scroller.scroll({ y: 0 })'
          args: ''
          param: ''
          silent: true
      label: TO VIDEO
      type: group
    - steps:
        - type: extract
          param: entirePage
          selectorType: ai
          selector: null
          item: null
          default: ''
          silent: true
        - type: calc
          regex: /Key Takeaways for quick navigation/gmi
          func: match
          to: match
          param: entirePage
        - condition: '{{match.length}} > 0'
          type: calc
          func: list-update
          match: '{ "url": "{{item.url}}" }'
          prop: '{ "status": "done" }'
          list: g.videos
        - condition: '{{match.length}} > 0'
          type: say
          message: Video has already been commented.
        - condition: '{{match.length}} > 0'
          type: jump
          to: END
      label: HAS COMMENT
      type: group
    - steps:
        - type: extract
          selectorType: ai
          selector: null
          item: null
          param: entirePage
          default: ''
          silent: true
        - type: calc
          regex: /Comments are turned off/gmi
          func: match
          to: match
          param: entirePage
        - condition: '{{match.length}} > 0'
          type: calc
          func: list-update
          list: g.videos
          match: '{ "url": "{{item.url}}" }'
          prop: '{ "status": "done" }'
        - condition: '{{match.length}} > 0'
          type: say
          message: Video has comments turned off.
        - condition: '{{match.length}} > 0'
          type: jump
          to: END
      label: COMMENT OFF
      type: group
    - steps:
        - type: calc
          prop: '{ "status": "no-transcript" }'
          func: list-update
          list: g.videos
          match: '{ "url": "{{item.url}}" }'
        - type: say
          message: Video has no transcript.
        - type: jump
          to: END
      condition: '{{transcript}} ='
      label: NO TRANSCRIPT
      type: group
    - steps:
        - type: gpt
          prompt: >-
            You goal is to detect language of the given video transcript:


            {{transcript}}


            Please only output the main transcript language (one word) and
            nothing else, for example: "English", "Spanish", "Russian". If not
            sure about the language, output single word "English".


            Language:
          param: language
          silent: true
        - type: say
          message: 'Video language: {{language}}'
        - steps:
            - type: say
              message: SKIP {{language}}
            - type: jump
              to: END
          condition: >
            {{language}} =~
            ^(?!.*\b(English|Spanish|French|Italian|Portuguese)\b)(Vietnamese|Arabic|Japanese|Hindi|Persian|Korean|Urdu|Chinese|.{12,})
          label: SKIP LANG
          type: group
      condition: '{{p1}} = yes'
      label: DETECT LANGUAGE (OPTIONAL)
      type: group
    - steps:
        - type: calc
          func: list-update
          list: g.videos
          match: '{ "url": "{{item.url}}" }'
          prop: '{ "status": "done" }'
        - prompt: >-
            Please ignore previous instructions.


            Write a comment on a YouTube video page about the video.


            Follow the instructions:

            - Your comment should be short.

            - Your comment must be in a single ```markdown code block```.

            - Your comment must be in {{language}}.

            - [Reply format / Reply should contain if any]: {{config.style}}


            - Please pretend to be me, mimicking my style of communication:
            {{tone}}


            [Output framework]:


            ```markdown

            Comment

            ```


            -------

            For context and to understand the subject matter, use the [YOUTUBE
            VIDEO TRANSCRIPT]:


            {{transcript}}



            NEW COMMENT:
          type: gpt
          isolated: true
          param: gpt
        - func: extract-code
          to: reply
          index: first
          type: calc
          param: gpt
        - code: |-
            // check if comment box is found
            let cb = document.querySelector('ytd-comment-simplebox-renderer')
            if (cb) return true

            // wait for comment box to appear
            document.documentElement.scrollTop += window.innerHeight
            cb = await $harpa.waiter.wait(
              () => document.querySelector('ytd-comment-simplebox-renderer'),
              { timeout: 1500 })
            return !!cb
          param: commentBoxFound
          type: js
          args: ''
          silent: true
        - condition: '{{commentBoxFound}} = false'
          label: COMMENT BOX NOT FOUND
          steps:
            - message: >
                โ›” Could not find **Add a comment** button. Here is the entire
                summary:
              type: say
            - type: jump
              to: END
          type: group
        - type: click
          selector:
            - $matches:
                - $tag: YT-FORMATTED-STRING
                - $role: textbox
                - $id: simplebox-placeholder
                - $class: style-scope
                - $class: ytd-comment-simplebox-renderer
                - $attribute: role=textbox
                - $attribute: tabindex=0
                - $style: Roboto:14px:400:normal
                - $content: Add a commentโ€ฆ
                - $id: placeholder-area
                  traverse: '0'
                - $id: simple-box
                  traverse: '0:1:0'
                - $id: header
                  traverse: '0:4:0:1:0'
                - $id: sections
                  traverse: '0:0:4:0:1:0'
                - traverse: '1:0:0:4:0:1:0'
                  $id: comments
                - $anchor: Sort by
                  shift: '-136:51'
                - traverse: '-8:4:0:1:0'
                  $text: Sort by
              min: 6
            - $size: 1
          onFailure: skip
          selectorType: ai
          item: null
          showMore: false
          waitForIdle: false
          silent: true
        - type: paste
          text: '{{reply}}'
          selectorType: ai
          silent: true
        - selector:
            - $matches:
                - $tag: SPAN
                - $role: text
                - $class: yt-core-attributed-string
                - $class: yt-core-attributed-string--white-space-no-wrap
                - $attribute: role=text
                - $style: Roboto:14px:500:normal
                - $content: Comment
                - $id: submit-button
                  traverse: '0:0:0:0'
                - $id: buttons
                  traverse: '1:0:0:0:0'
                - $id: footer
                  traverse: '6:1:0:0:0:0'
                - $id: main
                  traverse: '7:6:1:0:0:0:0'
                - $id: thumbnail-input-row
                  traverse: '1:7:6:1:0:0:0:0'
                - $anchor: Cancel
                  shift: '92:0'
                - traverse: '-5:1:0:0:0:0'
                  $text: Cancel
              min: 7
            - $size: 1
          type: click
          selectorType: ai
          item: null
          showMore: false
          onFailure: skip
          waitForIdle: false
          silent: true
        - type: calc
          func: increment
          param: commentsCount
          delta: 1
        - steps:
            - type: say
              message: >
                โœ… **Task completed! {{commentsCount}} comments generated and
                posted.**
            - type: stop
          condition: '{{g.commentsPerRun}} = {{commentsCount}}'
          label: END TASK
          type: group
        - type: wait
          for: custom-delay
          silent: false
          delay: '{{g.commentsDelay}}'
      label: COMMENT
      type: group
    - label: END
      type: say
      message: ''
  condition: '{{type}} = comments'
  type: loop
  list: videos
- type: stop
Notice: Please read before using

This automation command is created by a community member. HARPA AI team does not audit community commands.

Please review the command carefully and only install if you trust the creator.

Contact us
HomeUse CasesGuidesPrivacy PolicyTerms of Service
CAN WE STORE COOKIES?
Our website uses cookies for the purposes of accessibility and security. They also allow us to gather statistics in order to improve the website for you. More info: Privacy Policy