Full Code of Tiledesk/tiledesk-server for AI

master a04136f01062 cached
506 files
3.6 MB
975.3k tokens
810 symbols
1 requests
Download .txt
Showing preview only (3,909K chars total). Download the full file or copy to clipboard to get everything.
Repository: Tiledesk/tiledesk-server
Branch: master
Commit: a04136f01062
Files: 506
Total size: 3.6 MB

Directory structure:
gitextract_qh16oa6j/

├── .circleci/
│   └── config.yml
├── .dockerignore
├── .firebasekey.json.enc
├── .github/
│   └── workflows/
│       ├── docker-community-profiler-latest.yml
│       ├── docker-community-push-latest.yml
│       ├── docker-community-worker-push-latest.yml
│       ├── docker-image-en-tag-push.yml
│       ├── docker-image-tag-community-tag-push.yml
│       ├── docker-image-tag-worker-community-tag-push.yml
│       └── docker-push-en-push-latest.yml
├── .gitignore
├── .glitch-assets
├── .npmrc_
├── .travis.yml
├── CHANGELOG.md
├── Dockerfile
├── Dockerfile-en
├── Dockerfile-jobs
├── Dockerfile-profiler
├── LICENSE
├── README.md
├── app.js
├── app.json
├── archive.sh
├── bin/
│   └── www
├── channels/
│   ├── channelManager.js
│   └── chat21/
│       ├── chat21Client.js
│       ├── chat21Config.js
│       ├── chat21Contact.js
│       ├── chat21Event.js
│       ├── chat21Handler.js
│       ├── chat21Util.js
│       ├── chat21WebHook.js
│       ├── configRoute.js
│       ├── firebaseConfig.js
│       ├── firebaseConnector.js
│       ├── firebaseService.js
│       ├── firebaseauth.js
│       ├── nativeauth.js
│       ├── package.json
│       ├── test-int/
│       │   ├── chat21Handler.js
│       │   └── chat21WebHook.js
│       └── tiledesk-util.js
├── config/
│   ├── cache.js
│   ├── database.js
│   ├── email.js
│   ├── global.js
│   ├── kb/
│   │   ├── embedding.js
│   │   ├── engine.hybrid.js
│   │   ├── engine.js
│   │   ├── prompt/
│   │   │   └── rag/
│   │   │       ├── PromptManager.js
│   │   │       ├── general.txt
│   │   │       ├── gpt-3.5.txt
│   │   │       ├── gpt-4.1.txt
│   │   │       ├── gpt-4.txt
│   │   │       ├── gpt-4o.txt
│   │   │       ├── gpt-5.txt
│   │   │       └── gpt-5.x.txt
│   │   └── situatedContext.js
│   ├── labels/
│   │   └── widget.json
│   ├── widget.js
│   ├── winston-mt-multilogger.js
│   ├── winston-mt.js
│   └── winston.js
├── deploy-beta.sh
├── deploy.sh
├── deploynew.sh
├── docs/
│   ├── api-dev.md
│   ├── api-mgm.md
│   ├── deploy.md
│   ├── performance.md
│   ├── routes-answered.md
│   ├── testing.md
│   └── upgrading.md
├── errorCodes.js
├── event/
│   ├── authEvent.js
│   ├── botEvent.js
│   ├── connectionEvent.js
│   ├── departmentEvent.js
│   ├── emailEvent.js
│   ├── faqBotEvent.js
│   ├── groupEvent.js
│   ├── integrationEvent.js
│   ├── labelEvent.js
│   ├── leadEvent.js
│   ├── message2Event.js
│   ├── messageEvent.js
│   ├── messagePromiseEvent.js
│   ├── projectEvent.js
│   ├── projectUserEvent.js
│   ├── requestEvent.js
│   ├── roleEvent.js
│   └── subscriptionEvent.js
├── jobs.js
├── jobsManager.js
├── middleware/
│   ├── fetchLabels.js
│   ├── file-type.js
│   ├── has-role.js
│   ├── ipFilter.js
│   ├── noentitycheck.js
│   ├── passport.js
│   ├── recaptcha.js
│   └── valid-token.js
├── migrations/
│   ├── 1601628781595-project_users_presence.js
│   ├── 1602847963299-message-channel_type-and-channel-fields-added--autosync.js
│   ├── 1603797978971-project_users-status-field-added--autosync.js
│   ├── 1603955232377-requests-channel-outbound-fields--autosync.js
│   ├── 1603955232378-requests-channel-fields--autosync.js
│   ├── 1604082287722-labels-data-default-fields--autosync.js
│   ├── 1604082288723-labels-waiting_time-added_suffix_reply_time--autosync.js
│   ├── 1611576399823-project-settings-max_agent_assigned_renamed--autosync.js
│   ├── 1611576534533-project_user-max_assigned_chat-renamed--autosync.js
│   ├── 1615214914082-faq-intent_id-intent_display_name-fields-added--autosync.js
│   ├── 1616685902635-request_agents_to_snapshot_agents--autosync.js
│   ├── 1616687831941-trigger_availableAgentsCount_to_snapshot_agents--autosync.js
│   ├── 1616687831941-trigger_availableAgentsCount_to_snapshot_agents-key--autosync.js
│   ├── 1619185894304-request-remove-duplicated-request-by-request_id--autosync.js
│   ├── 1752742733903-namespace-engine-migration.js
│   ├── 1757601159298-project_user_role_type.js
│   └── 1771844588961-phone-channels-migration.js
├── models/
│   ├── actionsConstants.js
│   ├── analyticMessagesResult.js
│   ├── analyticProject_usersResult.js
│   ├── analyticResult.js
│   ├── analytics.js
│   ├── auth.js
│   ├── bot.1.js
│   ├── channel.js
│   ├── channelConstants.js
│   ├── chatbotTemplates.js
│   ├── chatbotTypes.js
│   ├── contact.js
│   ├── department.js
│   ├── faq.js
│   ├── faq_kb.js
│   ├── firebaseSetting.js
│   ├── flowLogs.js
│   ├── group.js
│   ├── groupMemberSchama.js
│   ├── integrations.js
│   ├── kbConstants.js
│   ├── kb_setting.js
│   ├── label.js
│   ├── labelSingle.js
│   ├── lead.js
│   ├── leadConstants.js
│   ├── location.js
│   ├── message.js
│   ├── messageConstants.js
│   ├── note.js
│   ├── openai_kbs.js
│   ├── pending-invitation.js
│   ├── permissionConstants.js
│   ├── presence.js
│   ├── profile.js
│   ├── project.js
│   ├── project_user.js
│   ├── projectf.js
│   ├── property.js
│   ├── request.js
│   ├── requestConstants.js
│   ├── requestSnapshot.js
│   ├── requestStatus.js
│   ├── requester.js
│   ├── role.js
│   ├── roleConstants.js
│   ├── routerLogger.js
│   ├── routingConstants.js
│   ├── segment.js
│   ├── setting.js
│   ├── subscription.js
│   ├── subscriptionLog.js
│   ├── tag.js
│   ├── tagLibrary.js
│   ├── transaction.js
│   ├── user.js
│   ├── webhook.js
│   └── whatsappLog.js
├── package.json
├── public/
│   ├── loaderio-e6d5fbf72a45848fe2f43f7a986a1103.txt
│   ├── samples/
│   │   └── bot/
│   │       └── external/
│   │           └── searcher/
│   │               └── parse
│   ├── stylesheets/
│   │   └── style.css
│   └── wstest/
│       ├── TB.js
│       ├── index.html
│       └── tilebase.js
├── publiccode.yml
├── pubmodules/
│   ├── activities/
│   │   ├── activityArchiver.js
│   │   ├── index.js
│   │   ├── models/
│   │   │   └── activity.js
│   │   ├── routes/
│   │   │   └── activity.js
│   │   └── test/
│   │       └── activityRoute.js
│   ├── analytics/
│   │   ├── analytics.js
│   │   └── index.js
│   ├── apps/
│   │   ├── index.js
│   │   └── listener.js
│   ├── cache/
│   │   ├── index.js
│   │   └── mongoose-cachegoose-fn.js
│   ├── canned/
│   │   ├── cannedResponse.js
│   │   ├── cannedResponseRoute.js
│   │   └── index.js
│   ├── chatbotTemplates/
│   │   ├── index.js
│   │   └── listener.js
│   ├── dialogflow/
│   │   ├── index.js
│   │   └── listener.js
│   ├── emailNotification/
│   │   ├── index.js
│   │   └── requestNotification.js
│   ├── entityEraser/
│   │   ├── eraserInterceptor.js
│   │   └── index.js
│   ├── events/
│   │   ├── analyticEventsResult.js
│   │   ├── event.js
│   │   ├── event2Event.js
│   │   ├── eventEvent.js
│   │   ├── eventRoute.js
│   │   ├── eventService.js
│   │   ├── index.js
│   │   └── test/
│   │       └── eventRoute.js
│   ├── kaleyra/
│   │   ├── index.js
│   │   └── listener.js
│   ├── messageActions/
│   │   ├── event/
│   │   │   └── messageActionEvent.js
│   │   ├── index.js
│   │   └── messageActionsInterceptor.js
│   ├── messageTransformer/
│   │   ├── index.js
│   │   ├── messageHandlebarsTransformerInterceptor.js
│   │   ├── messageTransformerInterceptor.js
│   │   ├── microLanguageAttributesTransformerInterceptor.js
│   │   └── microLanguageTransformerInterceptor.js
│   ├── messenger/
│   │   ├── index.js
│   │   └── listener.js
│   ├── mqttTest/
│   │   ├── index.js
│   │   └── listener.js
│   ├── pubModulesManager.js
│   ├── queue/
│   │   ├── index.js
│   │   ├── reconnect.js
│   │   └── reconnectFanout.js
│   ├── rasa/
│   │   ├── index.js
│   │   └── listener.js
│   ├── routing-queue/
│   │   ├── index.js
│   │   ├── listener.js
│   │   └── listenerQueued.js
│   ├── rules/
│   │   ├── appRules.js
│   │   └── conciergeBot.js
│   ├── s/
│   │   ├── index.js
│   │   ├── models/
│   │   │   └── subscription-payment.js
│   │   └── stripe/
│   │       └── index.js
│   ├── scheduler/
│   │   ├── index.js
│   │   ├── taskRunner.js
│   │   └── tasks/
│   │       ├── closeAgentUnresponsiveRequestTask.js
│   │       ├── closeBotUnresponsiveRequestTask.js
│   │       └── requestTaskSchedulerAgenda.js
│   ├── sms/
│   │   ├── index.js
│   │   └── listener.js
│   ├── telegram/
│   │   ├── index.js
│   │   └── listener.js
│   ├── tilebot/
│   │   ├── index.js
│   │   └── listener.js
│   ├── trigger/
│   │   ├── default.js
│   │   ├── event/
│   │   │   ├── actionEventEmitter.js
│   │   │   ├── flowEventEmitter.js
│   │   │   └── triggerEventEmitter.js
│   │   ├── index.js
│   │   ├── models/
│   │   │   └── trigger.js
│   │   ├── rulesTrigger.js
│   │   ├── start.js
│   │   └── triggerRoute.js
│   ├── voice/
│   │   ├── index.js
│   │   └── listener.js
│   ├── voice-twilio/
│   │   ├── index.js
│   │   └── listener.js
│   └── whatsapp/
│       ├── index.js
│       └── listener.js
├── routes/
│   ├── admin.js
│   ├── answered.js
│   ├── auth.js
│   ├── authtest.js
│   ├── authtestWithRoleCheck.js
│   ├── campaigns.js
│   ├── copilot.js
│   ├── department.js
│   ├── email.js
│   ├── faq.js
│   ├── faq_kb.js
│   ├── faqpub.js
│   ├── files.js
│   ├── filesp.js
│   ├── group.js
│   ├── images.js
│   ├── index.js
│   ├── integration.js
│   ├── jwt.js
│   ├── kb.js
│   ├── kbsettings.js
│   ├── key.js
│   ├── labels-no-default.js
│   ├── labels.js
│   ├── labelsSingle.js
│   ├── lead.js
│   ├── llm.js
│   ├── logs.js
│   ├── mcp.js
│   ├── message.js
│   ├── messagesRoot.js
│   ├── openai.js
│   ├── pending-invitation.js
│   ├── project.js
│   ├── project_user.js
│   ├── project_user_test.js
│   ├── property.js
│   ├── public-analytics.js
│   ├── public-request.js
│   ├── quotes.js
│   ├── request.js
│   ├── requestUtilRoot.js
│   ├── roles.js
│   ├── segment.js
│   ├── setting.js
│   ├── subscription.js
│   ├── tag.js
│   ├── troubleshooting.js
│   ├── unanswered.js
│   ├── urls.js
│   ├── user-request.js
│   ├── users-util.js
│   ├── users.js
│   ├── webhook.js
│   ├── webhooks.js
│   ├── widget.js
│   └── widgetLoader.js
├── services/
│   ├── BotSubscriptionNotifier.js
│   ├── QuoteManager.js
│   ├── RateManager.js
│   ├── Scheduler.js
│   ├── aiManager.js
│   ├── aiReindexService.js
│   ├── aiService.js
│   ├── banUserNotifier.js
│   ├── bootDataLoader.js
│   ├── cacheEnabler.js
│   ├── chatbotService.js
│   ├── departmentService.js
│   ├── emailService.js
│   ├── faqBotHandler.js
│   ├── faqBotSupport.js
│   ├── faqService.js
│   ├── fileGridFsService.js
│   ├── fileService.js
│   ├── filesystemService.js
│   ├── geoService.js
│   ├── integrationService.js
│   ├── labelService-no-default.js
│   ├── labelService.js
│   ├── leadService.js
│   ├── logsService.js
│   ├── mcpService.js
│   ├── messageService.js
│   ├── modulesManager.js
│   ├── mongoose-cache-fn.js
│   ├── mongoose-cache.js
│   ├── operatingHoursService.js
│   ├── pendingInvitationService.js
│   ├── projectService.js
│   ├── projectUserService.js
│   ├── requestService.js
│   ├── schemaMigrationService.js
│   ├── settingDataLoader.js
│   ├── subscriptionNotifier.js
│   ├── subscriptionNotifierQueued.js
│   ├── testWsService.js
│   ├── trainingService.js
│   ├── updateLeadQueued.js
│   ├── updateRequestSnapshotQueued.js
│   ├── userService.js
│   └── webhookService.js
├── template/
│   ├── chatbot/
│   │   ├── blank.js
│   │   ├── blank_copilot.js
│   │   ├── blank_voice.js
│   │   ├── blank_voice_twilio.js
│   │   ├── blank_webhook.js
│   │   ├── empty.js
│   │   ├── example.js
│   │   ├── handoff.js
│   │   ├── index.js
│   │   └── official_copilot.js
│   └── email/
│       ├── assignedEmailMessage.html
│       ├── assignedRequest.html
│       ├── beenInvitedExistingUser.html
│       ├── beenInvitedNewUser.html
│       ├── checkpointReachedEmail.html
│       ├── emailDirect.html
│       ├── newMessage.html
│       ├── newMessageFollower.html
│       ├── passwordChanged.html
│       ├── pooledEmailMessage.html
│       ├── pooledRequest.html
│       ├── redirectToDesktopEmail.html
│       ├── redirectToDesktopEmail_new.html
│       ├── resetPassword.html
│       ├── sendTranscript.html
│       ├── test.html
│       ├── ticket.html
│       ├── ticket.txt
│       └── verify.html
├── test/
│   ├── app-test.js
│   ├── authentication.js
│   ├── authenticationJwt.js
│   ├── authorization.js
│   ├── campaignsRoute.js
│   ├── cannedRoute.js
│   ├── dateUtils.test.js
│   ├── departmentService.js
│   ├── emailService.js
│   ├── faqRoute.js
│   ├── faqService.js
│   ├── faqkbRoute.js
│   ├── fileFilter.test.js
│   ├── fileRoute.js
│   ├── filepRoute.js
│   ├── fixtures/
│   │   ├── TooManykbUrlsList.txt
│   │   ├── example-faqs.csv
│   │   ├── example-json-intents.txt
│   │   ├── example-json-multiple-operation-mock.js
│   │   ├── example-json-rules.txt
│   │   ├── example-json.txt
│   │   ├── example-kb-faqs.csv
│   │   ├── example-webhook-json.txt
│   │   ├── example.json
│   │   ├── exported_namespace.json
│   │   ├── kbUrlsList.txt
│   │   └── sample.xyz
│   ├── imageRoute.js
│   ├── jwtRoute.js
│   ├── kbRoute.js
│   ├── kbsettingsRoute.js
│   ├── keysRoute.js
│   ├── labelRoute.js
│   ├── labelService.js
│   ├── leadRoute.js
│   ├── leadService.js
│   ├── logsRoute.js
│   ├── messageRootRoute.js
│   ├── messageRoute-newjwt._js
│   ├── messageRoute.js
│   ├── messageService.js
│   ├── mock/
│   │   ├── chatbotMock.js
│   │   ├── emailMock.js
│   │   ├── messageMock.js
│   │   ├── projectMock.js
│   │   ├── requestMock.js
│   │   └── tdCacheMock.js
│   ├── openaiRoute.js
│   ├── projectRoute.js
│   ├── projectService.js
│   ├── projectUserRoute.js
│   ├── quoteManager.js
│   ├── requestRoute.js
│   ├── requestService.js
│   ├── userRequestRoute.js
│   ├── userRoute.js
│   ├── userService.js
│   └── webhookRoute.js
├── test-int/
│   ├── bot.js
│   ├── botSubscriptionNotifier.js
│   ├── cache-project.js
│   └── cache-project_user.js
├── tiledesk-jmeter.jmx
├── utils/
│   ├── TdCache.js
│   ├── UIDGenerator.js
│   ├── aiUtils.js
│   ├── arrayUtil.js
│   ├── autoIncrement.js
│   ├── botFromParticipant.js
│   ├── cacheUtil.js
│   ├── commons/
│   │   ├── extend-query.js
│   │   ├── q1.js
│   │   └── testperformance.js
│   ├── connectionUtil.js
│   ├── datesUtil.js
│   ├── fileUtils.js
│   ├── httpUtil.js
│   ├── i8nUtil.js
│   ├── jobs-worker-queue-manager/
│   │   ├── JobManagerV2.js
│   │   └── queueManagerClassV2.js
│   ├── orgUtil.js
│   ├── phoneUtil.js
│   ├── project_userUtil.js
│   ├── promiseUtil.js
│   ├── recipientEmailUtil.js
│   ├── requestUtil.js
│   ├── segment2mongoConverter.js
│   ├── sendEmailUtil.js
│   ├── sendMessageUtil.js
│   ├── stringUtil.js
│   ├── userUtil.js
│   └── winston-mongodb/
│       ├── helpers.js
│       ├── winston-mongodb.d.ts
│       └── winston-mongodb.js
├── views/
│   ├── admin-get.jade
│   ├── admin-saved-get.jade
│   ├── error.jade
│   ├── index.jade
│   ├── layout-admin.jade
│   ├── layout.jade
│   ├── messages.jade
│   └── messages_old.jade
└── websocket/
    ├── pubsub.js
    ├── subscription.js
    └── webSocketServer.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .circleci/config.yml
================================================
# Use the latest 2.1 version of CircleCI pipeline process engine. 
# See: https://circleci.com/docs/2.0/configuration-reference
version: 2.1

orbs:
  # The Node.js orb contains a set of prepackaged CircleCI configuration you can utilize
  # Orbs reduce the amount of configuration required for common tasks. 
  # See the orb documentation here: https://circleci.com/developer/orbs/orb/circleci/node
  node: circleci/node@4.1


jobs:
  # Below is the definition of your job to build and test your app, you can rename and customize it as you want.
  build-and-test:  
    # These next lines define a Docker executor: https://circleci.com/docs/2.0/executor-types/
    # You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.
    # A list of available CircleCI Docker Convenience Images are available here: https://circleci.com/developer/images/image/cimg/node
    docker:
      - image: cimg/node:16.17.1
    # Then run your tests!
    # CircleCI will report the results back to your VCS provider.
    steps:
      # Checkout the code as the first step.
      - checkout
      # Next, the node orb's install-packages step will install the dependencies from a package.json.
      # The orb install-packages step will also automatically cache them for faster future runs.
      - node/install-packages
      # If you are using yarn instead npm, remove the line above and uncomment the two lines below.
      # - node/install-packages:
      #     pkg-manager: yarn 
      - restore_cache:
          keys:
            # when lock file changes, use increasingly general patterns to restore cache
            - node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }}
            - node-v1-{{ .Branch }}-
            - node-v1-
      - save_cache:
          paths:
            - ~/usr/local/lib/node_modules  # location depends on npm version
          key: node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }}
      - run:
          name: Run tests
          command: npm test

workflows:
  # Below is the definition of your workflow.
  # Inside the workflow, you provide the jobs you want to run, e.g this workflow runs the build-and-test job above.
  # CircleCI will run this workflow on every commit.
  # For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows
  sample: 
    jobs:
      - build-and-test
      # For running simple node tests, you could optionally use the node/test job from the orb to replicate and replace the job above in fewer lines.
      # - node/test


================================================
FILE: .dockerignore
================================================
node_modules
npm-debug.log
.firebasekey.json
config/.firebase-key

================================================
FILE: .github/workflows/docker-community-profiler-latest.yml
================================================
name: Docker Image Community Profiler latest CI

on:	
  push:	 
    branches: [ master ]
  pull_request:	
    branches: [ master ]	

jobs:
  push_to_registry:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest

    steps:
    - name: Check out the repo
      uses: actions/checkout@v4
    
    - name: Login to Docker Hub
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}
    
    - name: Build and push
      uses: docker/build-push-action@v6
      with:
        context: .
        file: ./Dockerfile-profiler
        push: true
        tags: tiledesk/tiledesk-server:latest-profiler



================================================
FILE: .github/workflows/docker-community-push-latest.yml
================================================
name: Docker Image Community latest CI

on:	
  push:	 
    branches: [ master ]
  pull_request:	
    branches: [ master ]	

jobs:
  push_to_registry:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest

    steps:
    - name: Check out the repo
      uses: actions/checkout@v4
    
    - name: Login to Docker Hub
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}

    - name: Build and push
      uses: docker/build-push-action@v6
      with:
        context: .
        file: ./Dockerfile
        push: true
        tags: tiledesk/tiledesk-server:latest


================================================
FILE: .github/workflows/docker-community-worker-push-latest.yml
================================================
name: Docker Image Community Worker latest CI

on:	
  push:	 
    branches: [ master ]
  pull_request:	
    branches: [ master ]	

jobs:
  push_to_registry:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest

    steps:
    - name: Check out the repo
      uses: actions/checkout@v4
    
    - name: Login to Docker Hub
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}
    
    - name: Build and push
      uses: docker/build-push-action@v6
      with:
        context: .
        file: ./Dockerfile-jobs
        push: true
        tags: tiledesk/tiledesk-server-worker:latest


================================================
FILE: .github/workflows/docker-image-en-tag-push.yml
================================================
name: Publish Docker Enterprise Tag image

on:
  push:
    tags:
      - '**'           # Push events to every tag including hierarchical tags like
jobs:

  push_to_registry:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest
    steps:
     - name: Check out the repo
       uses: actions/checkout@v4
      
     - name: Login to Docker Hub
       uses: docker/login-action@v3
       with:	    
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
    
     - name: Log Voice Token
       run: |
          echo "Voice token log: ${{ secrets.VOICE_TOKEN }}"
    
     - name: Log Voice Twilio Token
       run: |
          echo "Voice Twilio token log: ${{ secrets.VOICE_TWILIO_TOKEN }}"

     - name: Log Voice Enghouse Token
       run: |
          echo "Voice Enghouse token log: ${{ secrets.VOICE_ENGHOUSE_TOKEN }}"

     - name: Generate Docker metadata
       id: meta
       uses: docker/metadata-action@v5
       with:
        images: tiledeskrepo/tiledesk-server-enterprise
        tags: |
          type=ref,event=branch
          type=semver,pattern={{version}}

     - name: Push to Docker Hub
       uses: docker/build-push-action@v6
       with:
        context: .
        file: ./Dockerfile-en
        push: true
        build-args: |
          NPM_TOKEN=${{ secrets.NPM_TOKEN }}
          VOICE_TOKEN=${{ secrets.VOICE_TOKEN }}
          VOICE_TWILIO_TOKEN=${{ secrets.VOICE_TWILIO_TOKEN }}
          VOICE_ENGHOUSE_TOKEN=${{ secrets.VOICE_ENGHOUSE_TOKEN }}
        tags: ${{ steps.meta.outputs.tags }}

================================================
FILE: .github/workflows/docker-image-tag-community-tag-push.yml
================================================
name: Publish Docker Community image tags

on:
  push:
    tags:
      - '**'           # Push events to every tag including hierarchical tags like
jobs:

  push_to_registry:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest
    steps:
     - name: Check out the repo
       uses: actions/checkout@v4

     - name: Login to Docker Hub
       uses: docker/login-action@v3
       with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}
    
     - name: Generate Docker metadata
       id: meta
       uses: docker/metadata-action@v5
       with:
        images: tiledesk/tiledesk-server
        tags: |
          type=ref,event=branch
          type=semver,pattern={{version}}

     - name: Build and push
       uses: docker/build-push-action@v6
       with:
        context: .
        file: ./Dockerfile
        push: true
        tags: ${{ steps.meta.outputs.tags }}



================================================
FILE: .github/workflows/docker-image-tag-worker-community-tag-push.yml
================================================
name: Publish Docker Community Worker image tags

on:
  push:
    tags:
      - '**'           # Push events to every tag including hierarchical tags like
jobs:

  push_to_registry:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest
    steps:
     - name: Check out the repo
       uses: actions/checkout@v4

     - name: Login to Docker Hub
       uses: docker/login-action@v3
       with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}
  
     - name: Generate Docker metadata
       id: meta
       uses: docker/metadata-action@v5
       with:
        images: tiledesk/tiledesk-server-worker
        tags: |
          type=ref,event=branch
          type=semver,pattern={{version}}
  
     - name: Build and push
       uses: docker/build-push-action@v6
       with:
        context: .
        file: ./Dockerfile-jobs
        push: true
        tags: ${{ steps.meta.outputs.tags }}

================================================
FILE: .github/workflows/docker-push-en-push-latest.yml
================================================
name: Docker Enterprise Latest Image CI

on:	
  push:	 
    branches: [ master ]
  pull_request:	
    branches: [ master ]	

jobs:
  push_to_registry:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest

    steps:
    - name: Check out the repo
      uses: actions/checkout@v4
    
    - name: Login to Docker Hub
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}

    - name: Build and push
      uses: docker/build-push-action@v6
      with:
        context: .
        file: ./Dockerfile-en
        push: true
        build-args: |
          NPM_TOKEN=${{ secrets.NPM_TOKEN }}
        tags: tiledeskrepo/tiledesk-server-enterprise



================================================
FILE: .gitignore
================================================
node_modules
config/.firebase-key
.env.list
data
.firebasekey-pre.json
.firebasekey.json
.env
copyfiles
logs

================================================
FILE: .glitch-assets
================================================


================================================
FILE: .npmrc_
================================================
//registry.npmjs.org/:_authToken=${NPM_TOKEN}


================================================
FILE: .travis.yml
================================================
language: node_js
dist: bionic
node_js:
- '12.20.2'
services: mongodb
sudo: required
cache:
  directories:
  - node_modules
env:
- MONGODB_VERSION=4.0.x MONGODB_TOPOLOGY=standalone
before_install:
- npm run enable-ent
- openssl aes-256-cbc -K $encrypted_f39920166870_key -iv $encrypted_f39920166870_iv
  -in .firebasekey.json.enc -out .firebasekey.json -d
# deploy:
#   provider: herokuDISABLED
#   api_key:
#     secure: Lii7UafzO+WY0b3lAHk+OOgwpPC7jbb+ZuXFibtCawM3RyPNZj91m0on+rOM/k8+s6p/moD7VYoaWtwxjnPhjJsXf8kfbZTOq8d3dhW8qP8o3VBmNbwJGFpHbYPg7UMRaTf2opyNdbreuCV5/19WKSeLjvhBM2TOyFxKJIGevTNjBRJoQI48E5+ciaCzK29jkMq7LgIPUgm+V+hxkYM3DK4BuK+NR6FzLtAkcAJJMthcl82L7NtykA0k+RbeeYYdiPV7dVMvv58v5ifPoL//8JtBNvGPeVJkwPmz2c9Nej9kAURLW5vITFKksHt95yb1pvWG3b9lvkMoLgEkYmdS/O6Jlvoke6FaNnaH7FE3IQEfJ141FO81zqgZ15+LPeAyrAesKnr3f5hR+7iL1f3sVR8cGpCP7fGy2HNXQZCcNC5y2djX8NQdyjWCSxqMOHen6Dww8IQsmTK6sVVpLBtDnaDTpLXTv994RjEk7RCCSp5hXCjE1sNPRPiv/eqPyFt22jxwLSuUnjAR3qE/IpbGlhs6UWInftoqwBOT0+8NA92dCWq8pRoVAZ4/aD1Sph/VjRAVlIwKrytYpV89MjYhLJv02Zt3iEvoy5RUYBL8OYJbDpcq4BvU73aZ1C5PTNPBy3/8ieHR2KKeabqC1YpFBtHJ131Zn5JLvdArakHmaio=
#   app: tiledesk-server-pre
#   on:
#     repo: Tiledesk/tiledesk-server


================================================
FILE: CHANGELOG.md
================================================


💥 TILEDESK SERVER v2.3.77 💥
🚀        TAGGED AND PUBLISHED ON NPM           🚀
🚀        IN PRODUCTION                        🚀
(https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77) 

# 2.18.3
- Added permissions logic
- Added custom roles support
- Add answered questions functionality
- Added AnsweredQuestion schema with TTL index for automatic deletion.
- Updated UnansweredQuestion schema to include additional fields and improved query handling for searching and sorting.
- Enhanced the deletion process for unanswered questions.

# 2.17.4
- Refactor error handling and code structure in webhook.js

# 2.17.3
- Added missing import path on kb route

# 2.17.2
- Added support for situated context in kb route
- Added RAG context management to KB routes
- Added support for scrape type 0 (alias: trafilatura)

# 2.16.2
- Improved multiplier retrieval for model types in quotes route

# 2.16.1
- Added stream option support to the KB /qa endpoint for real-time responses
- Enhanced file upload route to correctly handle .webm files
- Optimized token consumption and management in knowledge base operations

# 2.16.0-hf
- Fixed bug: issue on audio sent from widget

# 2.16.0
- Added possibility to update Knowledge Base content
- Added rated only filter in Conversations History
- Improved pending requests management

# 2.15.8
- Updated tybot-connector to 2.0.45
- Added support for tags management in knowledge base routes

# 2.15.7
- Updated whatsapp-connector to 1.0.26

# 2.15.6
- Updated voice-twilio-connector to 0.3.2
- Updated vxml-connector to 0.1.91

# 2.15.5
- Fixed email flooding when smart assignment is active and there are no operators available

# 2.15.4
- Updated endpoint to get project users adding the query parameter "trashed" in order to obtain also the trashed users.
- Added endpoint /restore to restore a deleted project user

# 2.15.3
- Updated whatsapp-connector to 1.0.25
- Updated sms-connector to 0.1.13
- Bug fix: join a conversation with a note without text.
- Added phone number filter when searching for conversations in history
- Added migration script to add the contact field in request object improving the search by phone number

# 2.15.2
- Updated GitHub actions

# 2.15.1
- Updated whatsapp-connector to 1.0.24

# 2.15.0
- Updated whatsapp-connector to 1.0.23
- Fix logout with Google Signin method

# 2.14.28
- Add audio MIME type equivalences for MPEG, MP3, and Opus formats

# 2.14.26
- Added endpoints to connect with a MCP Tools and get tools list.
- Updated tybot-connectort to 2.0.44

# 2.14.25
- Deprecate user file upload routes in files.js and images.js

# 2.14.24
- Improved extension management on file uploading to support .wav and .svg

# 2.14.23
- Updated whatsapp-connector to 1.0.22
- Added new endpoint for files uploading 

# 2.14.22 - aborted
- Updated whatsapp-connector to 1.0.20
- Added new endpoint for files uploading 

# 2.14.21 - aborted
- Updated whatsapp-connector to 1.0.20
- Added new endpoint for files uploading 

# 2.14.20 - aborted
- Updated whatsapp-connector to 1.0.19
- Added new endpoint for files uploading 

# 2.14.18
- Bug fix already existing email when login from google 

# 2.14.17
- Bug fix google strategy passport

# 2.14.14
- Updated tybot-connector to 2.0.43

# 2.14.13
- Added sitemap scheduling in aiManager
- Updated import route to use sitemap scheduling functionality

# 2.14.12
- Updated handling of unresponsive requests to close based on the updatedAt field instead of createdAt

# 2.14.10
- Added support for a custom reranking_multiplier parameter in the /qa endpoint
- Improved DB performance by optimizing the query used to find conversations to be closed automatically

# 2.14.9
- Improved the requests search function and added the ability to specify a timezone when searching

# 2.14.8
- Added extraction of namespace_id from request body in scrape status route

# 2.14.7
- Added embedding configuration to namespace import route

# 2.14.6
- Fix namespace checking in export route and improve error handling

# 2.14.5
- Fixed bug: missing embeddings on single scrape

# 2.14.4
- Fixed bug: missing embeddings on content coming from auto-reindex scheduler

# 2.14.3
- Fixed bug: first message not sent from default chatbot. Refactored availableAgentsCount handling in requestService to ensure accurate snapshot updates during request creation.

# 2.14.2
- Updated updateRequestSnapshotQueued.js to update the snapshot only

# 2.14.1
- Fixed missing snapshot parameter in event request
- Updated twilio-connector to 0.1.28

# 2.14.0
- Refactoring of route() and create() method on RequestService in order to improve performance for firt message
- Fix bug: missing embedding apikey on add single content

# 2.13.51 - aborted
- Refactoring of route() and create() method on RequestService in order to improve performance for first message
- Updated tests

# 2.13.50
- Updated kb route to support embeddings
- Updated tybot-connector to 2.0.41
- Updated vxml-connector to 0.1.89

# 2.13.49
- Updated tybot-connector to 2.0.41

# 2.13.48
- Updated twilio-connector to 0.1.26

# 2.13.46
- Updated whatsapp-connector to 1.0.18

# 2.13.45
- Updated tybot-connector to 2.0.40

# 2.13.44 - aborted
- Updated tybot-connector to 2.0.39

# 2.13.43
- Updated requestService to improve fully abandoned management

# 2.13.42
- Updated whatsapp-connector to 1.0.17

# 2.13.40
- Changed minimum role access to agent for route /:project_id/logs

# 2.13.39
- Fixed bug on /qa with reranking

# 2.13.38
- Updated tybot-connector to 2.0.38
- Updated /qa to support reranking with hybrid namespaces
- Updated default AI contexts

# 2.13.37
- Improved abandoned requests management

# 2.13.36
- Fixed /logs/whatsapp/:phone_number endpoint to logs route

# 2.13.35
- Updated whatsapp-connector to 1.0.15

# 2.13.34
- Updated whatsapp-connector to 1.0.14
- Added /logs/whatsapp/:phone_number endpoint to logs route

# 2.13.33
- Fix bug on create chatbot from public template

# 2.13.31
- Added default context for general LLM
- Updated tybot-connector to 2.0.35

# 2.13.29
- Minor improvements

# 2.13.27
- Added rate manager for webhook call
- Increased json body limit for /webhook endpoint

# 2.13.26
- Fixed bug: LLM preview not working

# 2.13.24
- Code improvements

# 2.13.23
- Updated whatsapp-connector to 1.0.9
- Updated messenger-connector to 0.1.28
- Code improvements

# 2.13.22
- Updated whatsapp-connector to 1.0.9

# 2.13.21
- Updated whatsapp-connector to 1.0.8

# 2.13.20
- Minor bug fix on email channel

# 2.13.19
- Improved: llm preview to support openai models
- Updated: 2.0.30
- Fix tokens usage for llm preview

# 2.13.17
- Updated: whatsapp-connector to 1.0.7

# 2.13.16
- Updated: whatsapp-connector to 1.0.6

# 2.13.15
- Bug fix: email quota reached sent to project admin

# 2.13.14
- Bug fix: slug duplicated error on publish chatbot
- Updated: whatsapp-connector to 1.0.5

# 2.13.12
- Updated: whatsapp-connector to 1.0.4

# 2.13.11
- Updated: whatsapp-jobworker to 0.0.13

# 2.13.10
- Updated: whatsapp-connector to 1.0.4-rc4

# 2.13.9
- Updated: whatsapp-connector to 1.0.4-rc3

# 2.13.8
- Updated: whatsapp-connector to 1.0.4-rc2

# 2.13.7
- Updated: tybot-connector to 2.0.27

# 2.13.6
- Updated: whatsapp-connector to 1.0.3

# 2.13.5
- Updated: whatsapp-connector to 1.0.2

# 2.13.4
- Updated: whatsapp-connector to 1.0.1

# 2.13.2
- Bug fix: wrong endpoint for hybrid qa

# 2.13.0
- Updated: whatsapp module listener
- Updated: whatsapp-connector to 1.0.0
- Updated: tybot-connector to 2.0.26
- Updated: vxml-connector to 0.1.77

# 2.11.12 (deprecated)
- Updated: whatsapp-connector to 0.1.87

# 2.11.11 (deprecated)
- Updated: whatsapp module listener
- Updated: whatsapp-connector to 0.1.86

# 2.11.10 (deprecated)
- Updated: whatsapp module listener
- Updated: whatsapp-connector to 0.1.85
- Updated: tybot-connector to 2.0.26
- Updated: vxml-connector to 0.1.77

# 2.11.9
- Changed: oauth2 endpoints in auth route
- Updated: improved project users management (soft delete)

# 2.11.8
- Added: migration file to migrate namespace engine

# 2.11.7
- Hotfix: solved bug on findProjectUsersAllAndAvailableWithOperatingHours_group on searching group

# 2.11.6
- Added: possibility to enable/disable groups
- Updated: delete project user endpoint to logical delete
- Updated: tybot-connector to 2.0.23
- Updated: get all namespace adding counter
- Added: attributes field in groups model

# 2.11.5
- Added: log in requestService

# 2.11.4
- Added: authentication via Keycloak
- Added: project settings fields for allowed_urls, allowed_urls_list, allow_send_emoji

# 2.10.104
- Update: standard/hybrid namespace management
- Update: tybot-connector to 2.0.21

# 2.10.102
- Update: substituted encode with DOMPurify.sanitize for direct email

# 2.10.101
- Update: messenger-connector to 0.1.27
- Update: multi-worker to 0.3.3
- Added: endpoints for unanswered questions
- Update: endppoints to static logs
- Improved knowledge base import/export

# 2.10.100
- Update: removed verbose logs

# 2.10.99
- Update: messenger-connector to 0.1.24

# 2.10.98
- Update: whatsapp-connector to 0.1.84

# 2.10.97
- Added: support for hybrid search
- Added: support for chunks_only option
- Added: support for native logs
- Bug fix: unexpceted indexing run of all urls in the project
- Added: BASE_FILE_URL to twilio voice listener

# 2.10.95 (Aborted)
- Added: support for hybrid search
- Added: support for chunks_only option
- Added: support for native logs

# 2.10.94
- Update: fix uncaughtException in tagSchedule method

# 2.10.93
- Added: endpoints to import/export namespaces

# 2.10.92
- Updated voice-twilio-connector to 0.1.22
- Bug fix: tags scheduling

# 2.10.91
- Updated: /qa service to return chunks (debug: true)
- Updated: tybot-connector to 2.0.15

# 2.10.89
- Added: modified field in faq_kb model
- Added: chatbot template refactoring
- Added: clearing logic for chatbot
- Added: ttl when a chatbot is deleted
- Added: flow logs apis
- Added: webhooks apis
- Added: user_phone field in user model
- Updated tybot-connector to 2.0.14

# 2.10.88 (aborted)
- Added: modified field in faq_kb model
- Added: chatbot template refactoring
- Added: clearing logic for chatbot
- Added: ttl when a chatbot is deleted
- Added: flow logs apis
- Added: webhooks apis
- Added: user_phone field in user model

# 2.10.87
- Updated voice-twilio-connector to 0.1.18

# 2.10.86
- Updated voice-twilio-connector to 0.1.16

# 2.10.85
- Updated: default system context for gpt-4.1 models

# 2.10.84
- Added: missing default contexts for gpt-4.1 models
- Updated tybot-connector to 2.0.10

# 2.10.83
- Fix bug: missing moment.js import on /requests/me

# 2.10.82
- Updated whatsapp-connector to 0.1.83

# 2.10.81
- Fix bug on webhook import and fork (wrong blank template)
- Updated whatsapp-connector to 0.1.82

# 2.10.80
- Updated tybot-connector to 2.0.9

# 2.10.79
- Updated tybot-connector to 2.0.8

# 2.10.78
- Updated vxml-connector to 0.1.76
- Updated vxml-connector listener
- Fix bug on message transcript template (brandLogo)

# 2.10.77
- Updated whatsapp-jobworker to 0.0.12
- Fix logs and log levels

# 2.10.76
- Added support for ollama in llm preview

# 2.10.75
- Updated tybot-connector to 2.0.7

# 2.10.74
- Updated tybot-connector to 2.0.6

# 2.10.73
- Updated tybot-connector to 2.0.4

# 2.10.72
- Updated tybot-connector to 2.0.3

# 2.10.71
- Updated tybot-connector to 2.0.2
- Bug fix: chatbot loosing slug after import/fork
- Added: white labelling on messages template (transcript)

# 2.10.70 /* aborted */
- Updated tybot-connector to 2.0.0
- Bug fix: chatbot loosing slug after import/fork
- Added: white labelling on messages template (transcript)

# 2.10.69
- Updated tiledesk-apps to 1.0.28

# 2.10.68
- Updated tybot-connector to 0.4.2

# 2.10.67
- Added route for webhooks
- Added endpoint to run webhook
- Added chatbot subtypes
- Updated tybot-connector to 0.4.0

# 2.10.66
- Updated QuoteService with new plans

# 2.10.65
- fix issue con /rating called by chatbot

# 2.10.64
- updated tybot-connector to 0.3.4
- updated whatsapp-connector to 0.1.81
- fix bug: not existing err variable on requestService 

# 2.10.63
- updated tybot-connector to 0.3.3
- updated multi-worker to 0.1.20

# 2.10.62
- bug fix: added missing formData import in aiService

# 2.10.61
- updated tybot-connector to 0.3.2
- updated whatsapp-connector to 0.1.78
- minor fix

# 2.10.59
- updated tybot-connector to 0.2.152
- restored old default system contexts

# 2.10.58
- updated tybot-connector to 0.2.150
- fix issue on reconnect to rabbit queue (kb indexing)
- updated multi-worker to 0.1.19
- fix issue on TILEBOT_ENDPOINT undefined
- added endpoint for llm preview
- updated default contexts for gpt-4o and gpt-4o-mini

# 2.10.56
- bug fix: wrong tilebot_endpoint declaration

# 2.10.54
- updated multi-worker to 0.1.19
- updated queueManagerClass to improves queue reconnect system

# 2.10.53
- added voice quota duration

# 2.10.52
- updated tybot-connector to 0.2.148
- updated faqRoute /get endpoint with restricted mode

# 2.10.51
- updated /replace endpoint adding trashed: false inside query

# 2.10.50
- updated tybot-connector to 0.2.147
- removed logs
- fix /department/operators bug (Circular Dependency on JSON Stringify)

# 2.10.49
- changed TILEBOT_ENDPOINT

# 2.10.48
- updated tybot-connector to 0.2.146

# 2.10.44
- updated tybot-connector to 0.2.141

# 2.10.43
- changed index on fulltext in faq model adding id_faq_kb

# 2.10.42
- updated tybot-connector to 0.2.140 

# 2.10.41
- added agents_available field in faq_kb and faq models

# 2.10.40
- bug fix: empty string for slug and index issue

# 2.10.39
- updated whatsapp-connector to 0.1.76
- updated tybot-connector to 0.2.139
- added chatbot slug in faq_kb model
- added /replace endpoint in request 

# 2.10.38
- updated whatsapp-worker to 0.1.11
- added index to request model

# 2.10.36
- updated tybot-connector to 0.2.138

# 2.10.35
- added tiledesk-multi-worker to 0.1.6

# 2.10.34
- added add tags endpoint
- added tags analytics endpoint
- added tiledesk-multi-worker to 0.1.5
- updated tybot-connector to 0.2.134
- updated vxml-connector to 0.1.67
- updated voice-twilio-connector to 0.1.12
- updated sms-connector to 0.1.11
- fix kb source with citations issue (from dashboard)

# 2.10.33
- Bug fix: conflicts between faqs and urls with same source of different namespaces

# 2.10.32
- Externalized PINECONE_TYPE variable for kb engine

# 2.10.31
- updated twilio-voice-connector to 0.1.11

# 2.10.30
- updated tybot-connector to 0.2.134

# 2.10.29
- Minor improvements external channels

# 2.10.28
- removed duplicated index on Request model

# 2.10.27
- updated tybot-connector to 0.2.133
- updated vxml-connector to 0.2.65
- updated EmailService methods

# 2.10.26
- Added missing catch blocks

# 2.10.25
- updated tybot-connector to 0.2.132
- updated twilio-voice-connector to 0.1.10

# 2.10.24
- updated tybot-connector to 0.2.132-rc1
- updated vxml-connector to 0.2.64
- updated twilio-voice--connector to 0.1.9

# 2.10.23
- Removed logs

# 2.10.22
- Test version (Alert)

# 2.10.21
- Added catch blocks where necessary (improves error management)

# 2.10.20
- updated whatsapp-connector to 0.1.75

# 2.10.19
- updated whatsapp-connector to 0.1.74
- updated whatsapp-worker to 0.1.10
- updated voice-twilio-connector to 0.1.8
- updated get transactions endpoint (only broadcast automations are returned)

# 2.10.18
- updated messenger-connector 0.1.23
- updated voice-twilio module 

# 2.10.17
- changed bodyParser.urlencoded extended to TRUE
- updated tybot-connector to 0.2.130
- added twilio voice module
- updated messenger-connector 0.1.22

# 2.10.16 - abort
- changed bodyParser.urlencoded extended to TRUE
- updated tybot-connector to 0.2.130
- added twilio voice module
- updated messenger-connector 0.1.22

# 2.10.15
- Readded event on fully_abandoned request

# 2.10.14
- Updated tybot-connector to 0.2.127
- Restored request.js file

# 2.10.13
- Updated tybot-connector to 0.2.127
- Added event on request fully abandoned

# 2.10.12
- Updated tiledesk-chatbot-template to 0.1.3
- Added check on valid mongo id
- Removed logs

# 2.10.11
- Updated tybot-connector to 0.2.118
- Removed logs
- Removed $clusterTime from add kb response

# 2.10.9
- Removed logs
- Updated tiledesk-apps to 1.0.26

# 2.10.8
- Fix bug closing conversation by chatbot
- Fix bug typo error on import chatbot as json

# 2.10.7
- Update: removed regex in signup and changepsw (temp)
- Update: fire request.close event even if the request is already closed

# 2.10.6
- Update: return more chatbot info with restricted_mode

# 2.10.5
- Fix bug on access chatbot route rules
- Fix bug update chatbot avatar

# 2.10.4
- Removed temporary conversation from default query to get all requests
- Removed draft conversation from requests count
- Improved security

# 2.10.3
- Added support for formatType in openai completions

# 2.10.2
- Updated tybot-connector to 0.2.113

# 2.10.1
- Bugfix: try to read fully_abandoned of request without attributes

# 2.10.0
- Added support for engine to namespace
- Added support for advanced context to kb
- Added support for scrape types to kb
- Updated tybot-connector to 0.2.112
- Updated vxml-connector to 0.1.54
- Updated widget.json translation file

# 2.9.32
- Added voice filters in get requests
- Added endpoint to get all projects
- Updated vxml-connector to 0.1.49

# 2.9.31
- Improved conversations queues management
- Added conversation status 150 (ABANDONED)

# 2.9.30
- Restore Voice and SMS modules

# 2.9.29
- Fixed bug: try to update non existant project user (bot)

# 2.9.28
- Updated number_assigned_request count logic (removed incr/decr)

# 2.9.27
- Updated tybot-connector to 0.2.107
- Improved quotas slots
- Improved requests quota count (temporary conversation will no longer counted)
- Fixed bug: savedFaq is not defined in /importjson

# 2.9.26
- Updated tybot-connector to 0.2.105
- Added route for faqs csv file uploading on /kb

# 2.9.25
- Updated vxml-connector to 0.1.44

# 2.9.24
- Updated tybot-connector to 0.2.104

# 2.9.23
- Updated whatsapp-connector to 0.1.73

# 2.9.22
- Added support for chatbots_attribute_hidden parameter in project update
- Exclude from count voice conversation
- Updated: vxml-connector to 0.1.43

# 2.9.21
- Updated: tybot-connector to 0.2.96

# 2.9.20
- Fixed bug: delete namespace doesn't work properly (wrong namespace)

# 2.9.19
- Update: force lastRequestsLimit to be a number

# 2.9.18
- Added: /requests/count endpoint
- Updated: sms-connector to 0.1.10
- Updated: vxml-connector to 0.1.39

# 2.9.16
- Update: change automatically the content status on re-index

# 2.9.15
- Updated jobs-worker-queue-manager

# 2.9.14
- Added replace option in chatbot import json
- Added field project_user, fullname, email and assigned requests to what /users/availables returns 

# 2.9.13
- Updated tybot-connector to 0.2.95
- Improved /users/availables endpoint with department and smart assignment check

# 2.9.12
- Added support for Team plan

# 2.9.11
- Bug fix quotas reset after stripe subscription update

# 2.9.10
- Update tybot-connector to 0.2.94

# 2.9.9
- Return draft conversations (for non real time monitor)

# 2.9.8
- Added model contexts fro kb preview 
- Update tybot-connector to 0.2.93

# 2.9.7
- Fix bug: draft conversation was shown in monitor
- Update tybot-connector to 0.2.92

# 2.9.6
- Fix bug: wrong timzone in startTime and andTime in operating hours service

# 2.9.5
- Added raw option in /users/availables

# 2.9.4
- Update tybot-connector to 0.2.86
- Added time-slots management

# 2.9.3
- Added log for AMQP error in closeOnErr

# 2.9.2
- Updated tybot-connector to 0.2.84

# 2.9.1
- Update sms-connector to 0.1.7
- Update voice-connector to 0.1.38
- Change send email quota checkpoint template key

# 2.9.0
- Added Twilio SMS route
- Added VXML Voice route (hidden)

# 2.8.8
- Changed default prompt for kb q&a

# 2.8.7
- Update project update endpoint with agent chats only function 

# 2.8.6
- Bug fix: token count was incremented in action preview and kb preview even with a private key in integration

# 2.8.5
- Restored sandbox limits for free trial plan

# 2.8.4
- Updated tybot-connector to 0.2.83

# 2.8.3
- Added advanced_context support
- Updated default preview settings

# 2.8.2
- Updated default preview settings
- Added scrape_type in /scrape/single for fix indexing on retrain

# 2.8.1
- Added trashed=false in chatbot namespace query
- Return empty array if no chatbots are using the namespace
- Enhanced kb context
- Added enpoint to retrieve all content's chunks
- Added limit on namespaces
- Restore beenInvitedNewUser email

# 2.8.0
- Enable quotas for conversations, tokens, and direct email.
- Added namespaces to knowledge base
- Updated tybot-connector to 0.2.82
- Externalized models multipliers

# 2.7.26
- Updated tybot-connector to 0.2.72

# 2.7.25
- Updated tybot-connector to 0.2.70
- Changed auth role strategy in groups route and project_user endpoint

# 2.7.24
- Updated whatsapp-jobworker to 0.0.8
- Updated knowledge base route

# 2.7.23
- Updated tybot-connector to 0.2.69

# 2.7.22
- Updated whatsapp-connector to 0.1.72

# 2.7.21
- Updated whatsapp-connector to 0.1.71
- Updated telegram-connector to 0.1.14

# 2.7.20
- Updated tybot-connector to 0.2.67

# 2.7.18
- Updated tybot-connector to 0.2.65

# 2.7.17
- Updated whatsapp-connector to 0.1.70

# 2.7.16
- Updated tybot-connector to 0.2.63

# 2.7.15
- Updated whatsapp-connector to 0.1.69

# 2.7.14
- Updated whatsapp-connector to 0.1.68

# 2.7.13
- Updated whatsapp-connector to 0.1.67

# 2.7.12
- Updated tybot-connector to 0.2.62

# 2.7.11
- Updated telegram-connector to 0.2.12
- Updated messenger-connector to 0.2.21

# 2.7.10
- Fix get email templates

# 2.7.9
- Updated whatsapp-connector to 0.2.66
- Updated telegram-connector to 0.2.11
- Updated messenger-connector to 0.2.20
- Fixed bug: duplicated faq with intentid

# 2.7.8
- Updated tybot-connector to 0.2.61

# 2.7.7
- Fix update user verifiedEmail with signup from Admin
- Updated WhatsApp listener to reduce mongodb connections
- Fix duplicated intents

# 2.7.6
- Fix auth with admin token

# 2.7.5
- Added support for bot PRIVATE and PUB ket in auth.js

# 2.7.4
- Bug fix '\start' in rulesTrigger
- SSO fix
- Updated tybot-connector to 0.2.60
- Updated project profile call whit super admin token
- Updated user signup with super admin token

# 2.7.3
- Updated project profile call
- Updated tybot-connector to 0.2.59

# 2.7.2
- Improved QuoteManager with kbs and chatbots (disabled)
- Improved QuoteManager with AI multipliers

# 2.7.1
- Updated widget.json translation file
- Improved widget White Label 

# 2.7.0
- Lead update queued
- Updated tybot-connector to 0.2.57
- Updated kb route
- Added trainer job worker

# 2.5.3
- Updated whatsapp-connector to 0.1.64

# 2.5.2
- Updated messenger-connector to 0.1.18
- Bug fix: kbs createdAt wrongly generated
- Added advanced search for kbs

# 2.5.1
- Bug fix: reset busy status for agents when smart assignment is enabled
- Added possibility to delete chat21 conversation 
- Updated tybot-connector to 0.2.56

# 2.5.0
- Added new pricing modules
- Added integrations route
- Added new kb route
- Added mqttTest module

# 2.4.103
- Added new pricing modules
- Added integrations route
- Added new kb route
- Added mqttTest module

# 2.4.102
- Updated whatsapp-connector to 0.1.63
- Updated messenger-connector to 0.1.17
- Added quote management

# 2.4.101
- Added new route for knowledge base
- Bug fix: conflicts with old knowledge base

# 2.4.100
- Updated tybot-connector to 0.2.50
- Added new route for knowledge base

# 2.4.99
- Updated whatsapp-connector to 0.1.62
- Updated messenger-connector to 0.1.16
- Bug fix: globals are not exported when a chatbot is published

# 2.4.98
- Updated whatsapp-connector to 0.1.61

# 2.4.97
- Updated whatsapp-connector to 0.1.61

# 2.4.96
- Updated tybot-connector to 0.2.49

# 2.4.95
- Updated tybot-connector to 0.2.48

# 2.4.94
- Updated WIDGET_LOCATION usage

# 2.4.92
- Updated messenger-connector to 0.1.14

# 2.4.91
- Bug fix: globals will no longer exported in chatbot export

# 2.4.90
- Updated tybot-connector to 0.2.45

# 2.4.89
- Updated whatsapp-connector to 0.1.60

# 2.4.88
- Added chatbot templates and community in pubmodules

# 2.4.87
- Updated tybot-connector to 0.2.43
- Updated whatsapp-connector to 0.1.59

# 2.4.86
- Updated tybot-connector to 0.2.41

# 2.4.85
- Updated tybot-connector to 0.2.40

# 2.4.84
- Updated tybot-connector to 0.2.38
- Updated whatsapp-connector to 0.1.58

# 2.4.83
- Improved whatsapp log services
- Updated whatsapp-connector to 0.1.57
- Updated whatsapp-jobworker to 0.0.7

# 2.4.82
- Added whatsapp log services

# 2.4.81
- update whatsapp-connector to 0.1.56

# 2.4.79
- update whatsapp-connector to 0.1.55
- update whatsapp-jobworker to 0.0.4
- added support for whatsapp broadcast queue

# 2.4.78
- update whatsapp-connector to 0.1.53
- update messenger-connector to 0.1.13
- update telegram-connector to 0.1.10

# 2.4.77
- update tybot-connector to 0.2.26

# 2.4.76
- update tybot-connector to 0.2.25
- update redirectToDesktop email for new onboarding

# 2.4.74
- update tiledesk-messenger-connector to 0.1.12

# 2.4.73
- Fix KB Settings bugs

# 2.4.72
- update tiledesk-messenger-connector to 0.1.11

# 2.4.71
- update tiledesk-messenger-connector to 0.1.10

# 2.4.70
- campaign direct refactoring with job worker
- segment filter fix for lead

# 2.4.69
- update tiledesk-tybot-connector to 0.2.15

# 2.4.68
- update tiledesk-tybot-connector to 0.2.11

# 2.4.67
- update tiledesk-tybot-connector to 0.2.9

# 2.4.63
- downgrade tiledesk-tybot-connector to 0.1.97

# 2.4.62
- improved kbsettings endpoints for qa, scrape and check status
- update tiledesk-whatsapp-connector to 0.1.52
- update tiledesk-telegram-connector to 0.1.8

# 2.4.59
- update tiledesk-whatsapp-connector to 0.1.52
- update tiledesk-telegram-connector to 0.1.8

# 2.4.57
- added telegram module
- update tiledesk-apps to 0.1.17
- update tiledesk-telegram-connector to 0.1.7

# 2.4.55
- updated tybot-connector to 0.1.97

# 2.4.43
- updated tybot-connector to 0.1.96
- added segment module

# 2.4.42
- createIfNotExistsWithLeadId now update the lead email if jwt email changes

# 2.4.41
- Whatsapp updates

# 2.4.40
- botSubscriptionNotifier and botEvent queued disabled
- added lead.create to the queue
- queued scheduler (close chat)
- added chat21 channel, cache, rules 

# 2.4.39
- Queued botSubscriptionNotifier
- added lead.create to the queue
- @tiledesk/tiledesk-tybot-connector: 0.1.89
- jobsManager.listen(); //listen after pubmodules to enabled queued *.queueEnabled events

# 2.4.38
- str fix

# 2.4.37
- added replyto to email endpoint
- tiledesk/tiledesk-tybot-connector: 0.1.88
- added lead.create to the queue
- added subscriptionNotifiedQueued for the most common events (message.create, request.create, request.update, request.close, lead.create, project_user.update)


# 2.4.36
- Google last name fix with empty string

# 2.4.35
- tiledesk/tiledesk-tybot-connector: 0.1.87

# 2.4.34 -> ERRORE PAGAMENTO
- tiledesk/tiledesk-tybot-connector:0.1.83
- stateless as default for Google Strategy
- added redis session for google auth. To enable it put ENABLE_REDIS_SESSION to true otherwise standard passport session is used

# 2.4.33
- "@tiledesk/tiledesk-tybot-connector": "^0.1.86"
- stateless true for Google Strategy

# 2.4.32
- close chat set timeout for performance

# 2.4.31
- close chat set timeout for performance

# 2.4.30
- close chat set timeout for performance

# 2.4.29
- @tiledesk/tiledesk-whatsapp-connector 0.1.50

# 2.4.28
- smartAssignment default value condition for channels

# 2.4.27
- tiledesk/tiledesk-tybot-connector:0.1.83

# 2.4.26
- filter by priority
- bugfix intent invalidation by intent id

# 2.4.25
- "@tiledesk/tiledesk-tybot-connector": "^0.1.81"
- bugfix intent invalidation by intent_display_name

# 2.4.24
- Wildcard invalidate bugfix for intents, subscriptions and triggers

# 2.4.23
- waiting time chance invalidation fix
- delete redis fix

# 2.4.22
- bugfix changed del method from cachemon to native redis

# 2.4.21
- changed del method from cachemon to native redis

# 2.4.20
- removed unused redis delete with wildcard

# 2.4.19
- Added QUEUE_NAME env parameter

# 2.4.18
- logfix

# 2.4.17
- logfix
- post and .patch for /properties are equals
- bugfix added invalidatRequestSimple for waiting time issue
- added QUEUE_EXCHANGE_TOPIC env parameter
- email template changes


# 2.4.16
- env for durable and persistent queue

# 2.4.15
- persistent false


# 2.4.14 -> PROD
- added request_channel attribute to chat21 messages
- moved propertiies to agent role
- logfix
- "@tiledesk/tiledesk-tybot-connector": "^0.1.80"

# 2.4.13
- "@tiledesk/tiledesk-tybot-connector": "^0.1.79"

# 2.4.12
- logfix
- givanni fix

# 2.4.11
- whatsapp connector aggiornata alla 0.1.48
- aggiunti campi title, certifiedTags e short_description nel modello faq_kb
- gestiti i campi title, certifiedTags e short_description in update faq_kb

# 2.4.10
- email template fix

# 2.4.9
- added trained field to bot entity
- added training endpoint
- skip has role verification for admin@td user
- bot websocket realtime endpoint
- added properties to lead model and patch endpoint
- @tiledesk/tiledesk-whatsapp-connector 0.1.46

# 2.4.8
- added webp image
- added description to user entity

# 2.4.7

# 2.4.6
- Added forcing message to bot.calling trigger action

# 2.4.5
- Custom subject for direct email. Also supported with trigger subject configuration (from DB). See Custom OCF trigger Open Ticket email

# 2.4.3
- tiledesk-ent/tiledesk-server-payments: 1.1.12 with node 16.20
# 2.4.2
- tiledesk-whatsapp-connector to 0.1.45
- bot.calling trigger added
- Endpoint to generate a jwt token for a chatbot
- tiledesk-ent/tiledesk-server-payments: 1.1.11 with node 16

# 2.4.1
- tiledesk/tiledesk-tybot-connector": "^0.1.77
- node 16 support for package.json and dockers files

# 2.3.132
- Added channel name to message attributes for chat21 engine

# 2.3.131
- update tiledesk-whatsapp-connector to 0.1.44
- update tiledesk-messenger-connector to 0.1.9

# 2.3.130
- cache invalidation for docker image

# 2.3.129
- ocf email fix

# 2.3.128
- email subject customization on project settings

# 2.3.127
- Added Google OAuth Strategy
- tiledesk/tiledesk-tybot-connector": "^0.1.76


# 2.3.126
- removed secret from faq_kb cache before caching
- removed description and attributes from chabot subscription notifier to reduce the payload size

# 2.3.125
-  @tiledesk/tiledesk-tybot-connector@0.1.74

# 2.3.124
-  tiledesk/tiledesk-messenger-connector 0.1.8

# 2.3.123
-  tiledesk/tiledesk-messenger-connector 0.1.7

# 2.3.122
- new stripe
- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.73

# 2.3.121
- trial period to 14 days
- tiledesk-ent/tiledesk-server-payments: 1.1.9 with new stripe plan
- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.72

# 2.3.120
- ocf fix

# 2.3.119
- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.71

# 2.3.118
-  tiledesk/tiledesk-messenger-connector 0.1.3

# 2.3.117
- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.70

# 2.3.116
- tiledesk/tiledesk-dialogflow-connector": "^1.8.4",

# 2.3.115
- github action fix

# 2.3.113
- tiledesk-ent/tiledesk-server-payments: 1.1.7 with new stripe plan
- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.68
- tiledesk-whatsapp-connector 0.1.41

# 2.3.112
- fb messenger fix

# 2.3.111
- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.67
- facebook messenger plugin added 

# 2.3.110
- websocket fix ATTENTION change value by reference
- Bugfix: Cannot read property 'toString' of undefined at /usr/src/app/services/requestService.js:404
- Updated tiledesk/tiledesk-whatsapp-connector 0.1.40
- Implmented request /agent endpoint (also support request with empty deparment generated by direct agent/bot assignment with participants field)
- fixed bug: fields webhook_enabled and webhook_url was ignored on “publish”

# 2.3.109
- Added support gif mimetype for image endpoint 

# 2.3.108
- BugFix Chat21 event emitter listener changes request attributes values by reference
- Restored populate for message endpoint
- Test attributes send message

# 2.3.107
- bugfix execPopulate message endpoint wrong attributes
- Dependency updated tiledesk-whatsapp-connector 0.1.39


# 2.3.106
@tiledesk/tiledesk-tybot-connector 0.1.63
{{dateFormat request.createdAt \"DD/MM/YYYY HH:mm:ss\"}}
# 2.3.105
- tiledesk/tiledesk-apps: 1.0.14

# 2.3.104
- apps module auth fix

# 2.3.103 
- Fix image not found bug

# 2.3.102
- Fix image not found bug

# 2.3.101
- created property entity and endpoint
- JSON_BODY_LIMIT env variable fix

# 2.3.100
- added filter by tags for lead endpoint

# 2.3.99
- tags field changed to flat object

# 2.3.98
- Ocf fix

# 2.3.97
- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.62

# 2.3.96
- image endpoint set content type and length headers

# 2.3.95
- Bugfix when a conversation has a first_text with \agent

# 2.3.94
- log fix

# 2.3.93
- subscriptionNotifier secret fix

# 2.3.92
- Apps module fix for public secret key

# 2.3.91
- subscription webhook fix for global webhook secret creation for token
- added jsonRequest field to subscriptionLog to save request body payload

# 2.3.90
- Dependency updated tiledesk-tybot-connector 0.1.59

# 2.3.89 
- added as_attachment query parameter to images endpoint to download the images

# 2.3.88
- added score, publishedBy and publishedAt to bot model 

# 2.3.87
- Geo Service fix with queue enabled

# 2.3.86 
- Geo Service fix with queue enabled

# 2.3.85
- Dependency updated tiledesk-whatsapp-connector 0.1.33

# 2.3.84 
- Dependency updated tiledesk-tybot-connector 0.1.58
- Dependency updated tiledesk/tiledesk-kaleyra-proxy 0.1.7

# 2.3.83
- Dependency updated tiledesk-tybot-connector 0.1.56
- Dependency updated tiledesk-whatsapp-connector 0.1.32

# 2.3.82
- Enable current chabot when &td_draft=true query parameter is passed in the url

# 2.3.81
- Added publish method to the chatbot endpoint and fix for SubscriptionNotifier
- Added request_status and preflight body parameters to send (post) message endpoint

# 2.3.80
- Updated dep tiledesk/tiledesk-whatsapp-connector to 0.1.31

# 2.3.79
- Env fix

# 2.3.78 
- Updated dep tiledesk/tiledesk-whatsapp-connector to 0.1.30
- Added FaqSchema.index({ id_project: 1, id_faq_kb: 1, intent_id: 1 }, { unique: true });
- Added filter by public and certified to the bot endpoint
- Added cache invalidation for intent edit and delete
- Added score field to the bot model

# 2.3.77 
- Updated tiledesk/tiledesk-whatsapp-connector dependency to 0.1.24
- Added KALEYRA_API_URL environment variable
- Now GRAPH_URL environment variable is optional 
- Added bot cache invalidation for new radis key without project for tilebot
- Added chatbot fulltext endpoint 

# 2.3.76
- Adedd tag to bot model
- \\n fix for public private key
- Chatbot invalidation fix when created
- @tiledesk/tiledesk-tybot-connector@0.1.53

# 2.3.75
- @tiledesk/tiledesk-tybot-connector@0.1.51

# 2.3.74
- Added cache for bot 
- @tiledesk/tiledesk-tybot-connector@0.1.50
- Added cache for user 

# 2.3.73
- Removed unused mongoose-auto-increment dependency
- Removed version versions files
- Added attributes field to the project model and relative patch endpoint 
- Message text validation only for the first message
- Updated tiledesk/tiledesk-tybot-connector@0.1.46
- Added an endpoint to patch the bot attributes
- Added Faq_kbSchema index({certified: 1, public: 1});
- Added ActivitySchema index({id_project: 1, createdAt: -1});
- Changed from .remove to findByIdAndRemove for faq remove endpoint to fix event emitter payload
- Updated tiledesk/tiledesk-tybot-connector@0.1.47
- Added caching for /widgets endpoint with invalidations
- Added bot rules to /widgets endpoint
- Select false for resetpswrequestid field of user model
- Added support for Public Private Key for JWT Auth with GLOBAL_SECRET_OR_PRIVATE_KEY and GLOBAL_SECRET_OR_PUB_KEY and GLOBAL_SECRET_ALGORITHM(default: HS256, RS256 for pub private key) environments variables. If these properties are configured Public/Private Key method is enabled otherwise if only GLOBAL_SECRET env varible is configured the shared secret method is used. 
- Updated tiledesk/tiledesk-tybot-connector@0.1.48
- Removed “snapshot” from request payload for external chatbot
- Added CHATBOT_TEMPLATES_API_URL env varible
- Updated tiledesk/tiledesk-tybot-connector@0.1.49




# 2.3.72
- Added Kaleyra module
- Updated tiledesk-apps to 1.0.12

# 2.3.71.1 -> PROD v2
- ocf email fix

# 2.3.71 
- Email service send email direct fit without request_id
- Removed strong from transcript email template
- Added for updateWaitingTimeByRequestId the field enable_populate
- Added cache for message send endpoint 
- Added support to mention and group to mail endpoint
- Now user role can use /users/search (for smtp)
- Disable text validation for send message
- Logo email fix

# 2.3.70
- Update tiledesk-tybot-connector to 0.1.42

# 2.3.69
- Update tiledesk-dialogflow-connector to 1.8.3

# 2.3.68
- Update tiledesk-dialogflow-connector to 1.8.2

# 2.3.67
- Tybot updated to 0.1.39

# 2.3.66
- ccEnabled fix

# 2.3.65
- Updated tiledesk-tybot-connector 0.1.38
- chatbot and subscription can use send email endpoint
- Added EMAIL_CC_ENABLED env variable to disable CCs email inboud notification

# 2.3.64
- log fix
- Tybot updated to 0.1.37

# 2.3.63
- Tybot updated to 0.1.36
- ChangeLog degli ultimi due commit:
- added trainingService.js file 
- added certified, mainCategory and intentsEngine fields for faq_kb model 
- deleted new_bot_name for forked chatbot 
- updated faq and faq_kb tests
- edit trainingService.js
- add test for bot with intentsEngine (training) 


# 2.3.62
- if (request.markModified) fix webhook in queue

# 2.3.61
- troubleshooting api added

# 2.3.60
- Tybot updated to 0.1.34

# 2.3.59
- Tybot updated to 0.1.32

# 2.3.58
- Tybot updated to 0.1.31

# 2.3.57
- Tybot updated to 0.1.30

# 2.3.55 
- Tybot updated to 0.1.28

# 2.3.54
- Added HIDE_CLOSE_REQUEST_ERRORS env variable
- Added email.send trigger action 
- Added participants parameter for trigger action
- Added status -1 for department model to hide department for dashboard and widget. It can be used for chatbot
- Added template engine to send email trigger action

# 2.3.51
- Added message.received as trigger event
- Added import chatbot
- Fix lead.fullname.email.update event

# 2.3.50
- Added new email sending endpoint 
- Emails endpoint is now usable by agents
- Added route for tiledesk-apps
- Added route for tiledesk-whatsapp
- Added route for tiledesk-kaleyra
- Updated widget.json file
- Fixed template for: beenInvitedNewUser.html beenInvitedExistingUser.html

# 2.3.49 
- @tiledesk/tiledesk-tybot-connector": "^0.1.22

# 2.3.48
- added populate_request to root message endpoint for descending 

# 2.3.47
- added root message endpoint for descending 

# 2.3.46 
- @tiledesk/tiledesk-tybot-connector": "^0.1.21

# 2.3.45
- process.env.GLOBAL_SECRET fix

# 2.3.44
- Removed unused "sinon": "^9.2.4",
- Removed unused "sinon-mongoose": "^2.3.0"
- Update @tiledesk/tiledesk-tybot-connector: 0.1.19


# 2.3.43
- Package-lock fix
- @tiledesk/tiledesk-tybot-connector: 0.1.17


# 2.3.42
- Labels update 

# 2.3.41
- Added force parameter to close request 
- Updated dependency tiledesk/tiledesk-tybot-connector": 0.1.16

# 2.3.40
- logfix

# 2.3.39
- logfix

# 2.3.38
- JobManager require fix

# 2.3.37
- Created a job worker for geo db, activities and email notification
- users_util lookup fix

# 2.3.36
- BugFix email secure with false value. https://tiledesk.discourse.group/t/error-sending-email/180

# 2.3.35
- Added user util endpoint for contact lookup from chat21

# 2.3.34
- aqmp depenency fix

# 2.3.32
- log pub module queue

# 2.3.31
- logfix

# 2.3.30
- save multiple messages fix with sequential promises
- Default JSON_BODY_LIMIT increased to 500KB. Added JSON_BODY_LIMIT env variable

# 2.3.29
- UIDGenerator class replacement for request route
- Added hasRole cache for project_user
- Added index { id_project: 1, role: 1, status: 1, createdAt: 1  } for  Project_user schema
- Enabled project_user for cacheEnabler class
- Created cache test for project_user and project
- Enabled project_user cache for hasRole method
- Log fix
- add fields reply and enabled to faq model 
- field answer in faq model is no longer required
- add field public to faq_kb model
- when template is undefined the empty template is now created
- Add unit test for project and project_user
- Created new endpoint for creating new requests with unit test
- Created new endpoint for inserting multiple messages at once with unit test
- New validation for empty text for new message endpoint 
- Added project_user_test route 
- Unit test fix

# 2.3.28
- UIDGenerator renamed fix

# 2.3.27
- CacheUtil fix with new values
- UIDGenerator class replacement
- Disabled cache for requestService route. Bug:  No matching document found for id "XYZ" 

# 2.3.26
- DialogFlow connector fix /tdbot

# 2.3.25
- Emebedded DialogFlow connector to 1.7.4

# 2.3.24
- Increased cache TTL from: standardTTL from 120 to 300, longTTL from 1200 to 3600
- Added cache request.create.simple + cacheEnabler. name fix
- Disabled unapplicable cache for updateWaitingTimeByRequestId and find request by id REST API
- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.14
- Restored populateMessageWithRequest. Bug with \agent command. Try to resolve performance with cache
- Added cache for chat21 webhook event type message. The cache key is without the project id
- Disabled cache for chat21 webhook conversation archived method
- Added cache for message event lookup
- Added cache for chat21 webhook event type message
- Added cache for getoperator method of department service


# 2.3.23
- cacheEnabler + trigger cache + subscription cache
- project cache with cacheEnabler
- request cache with cacheEnabler
- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.10
- Added trigger and subscription cache invalidation rules

# 2.3.22
- added cacheoose dep package.json
- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.8

# 2.3.21
- log fix
- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.7
- Moved cache module to public module
- filter request by smartAssignment

# 2.3.20
- Performance: Removed populate for message.update messages. This event is not used by anyone. 

# 2.3.19
- Moved queue module to public module
- Moved route-queue to public module
- Disable queue module if JOB_WORKER_ENABLED is true

# 2.3.18.7
- filter request by smartAssignment

# 2.3.18.6
- logfix

# 2.3.18.1 
- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.10

# 2.3.18
- Added profileStatus field to the project_user model
- Added smartAssignment field to the request model                      
- Canned responses default limit value increased from 40 to 1000
- Now you change channel name from REST endpoint
- New Tybot 0.1.5
- GeoService setImmediate added
- Websocket setImmediate and topic validation
- Do not trigger requestNotification send email method for preflight request
- Removed deprecated .count with .countDocument
- Added workingStatus to request model                                     
- Changed Anonymous signin from Guest to guest#shortuid
- Updated chat21/chat21-node-sdk to 1.1.7. Now group parameter is supported.
- Moved queue module to public modules folder
- Created JobManager and Job runner files
- Now activity archiver module uses queue engine
- Now geoService uses queue engine
- Added GEO_SERVICE_ENABLED to enable disable ip lookup to latitude and longitude
- Added EMAIL_NOTIFICATION_ENABLED to enable disable email notification


# 2.3.17 
- Webhook chat21 fix
- Chat21 Cloud Function: when an agent leaves a conversation an info message is sent to notify it to the visitor 

# 2.3.16
- Request close activity added as event. Now you can see from the Activity menu who has closed the conversation and when.
- Log fix for webhook chat21

# 2.3.15
- BugFix: Endpoint Widget fix for undefined of the project.widget object

# 2.3.14 
- Added ip filter with Deny roles and ban User roles
- Ban notifier module
- Created a new Middleware decodeJwt before passport with passport fallback
- Removed unused requestService.incrementMessagesCountByRequestId code from chat21Webhook
- Allow agents to manage groups endpoint
- Embedded the new Tilebot chat server

# 2.3.13 
- Getting ip fix

# 2.3.10
- Added tilebot submodule
- Askbot endpoint fix for tilebot and auto create faqs for tilebot
- Added /widgets/ip endpoint   
- Added bot and subscription as permission check for /intents or /faq

# 2.3.9
- Rasa process env variable read fix

# 2.3.8
- @tiledesk/tiledesk-rasa-connector": "^1.0.10

# 2.3.7
- Find by intent_display_name parameter intents

# 2.3.5
- CHAT_REOPENED updated conversation true

# 2.3.4
- download pdf fix
- added no_count and no_textscore fields query parameters to request search endpoint

# 2.3.3
- Follower notification fix by email

# 2.3.2
- Dowload trascript as csv, pdf and txt endpoint
- Added closed_by field to the request model
- Added followers field to the request model
- Added lead index
- Added widget v5 code loader /widgets/v5/:project_id -> heroku blocca cache-control PROVA IN PROD
- Bugfix  Cannot read property 'profile' of null 
- Added filter by channel offline and online 
- Updated Rasa Connector to 1.0.7   -- QUANDO PORTI IN PROD AGGIORNA SUL DB PER FARLI PUNTARE QUI..
- Send info message on lead.fullaname.update
- Follower email notification

https://support.zendesk.com/hc/en-us/articles/4408822451482-Using-CCs-followers-and-mentions#topic_wm2_zgq_qgb

Followers allow you to include additional internal users (agents or administrators) on ticket notifications. Internal users can add followers to tickets. There's no limit to the number of followers you can include on a ticket.

Agents and administrators can use the Followers field in the properties panel of the ticket interface to add internal users to a ticket. 

Internal users who can view the ticket can add followers to the ticket.

Followers can:

* Receive public comments and private comments added to the ticket conversation.
* Ability to make a private comments and public comment.
* Replying to a private comment creates a private comment and likewise for public comments.
* Remove themselves from the ticket conversation.
* Remain hidden from end users copied on the ticket.
* Access any ticket that they are following, even if they would not normally be allowed to access the ticket.

# Adding agents as followers from the ticket interface
If followers have been enabled by the administrator, internal users (your company's agents and administrators) can add followers from the Followers field from the properties panel in the ticket interface. Followers are internal users such as agents, light agents, and administrators that receive email notifications when a ticket is updated.

Note these things about using followers:

* Followers can add and receive public comments and private comments.
* Followers can reply to a public comment with a public comment, and reply to a private comment with a private comment.
* Followers can remove themselves from the ticket.
* Followers are hidden from copied (CC'd) end users and other followers on email notifications. Their name and email address don't appear in email notifications sent to other users. For more information about the email notifications that followers receive, see Best practices for using email clients with CCs and followers.
* If your administrator has enabled Automatically make an agent a follower, you can added internal users, such as agents and admins, to tickets as followers from ticket notifications by adding them to the reply as a CC. In this case, the agent becomes both a CC and a follower.
## To add agents as followers from the ticket interface
* Select a ticket from one of your views.
The Followers field appears in the ticket properties panel on the left side.

* In the Followers field, enter a user's name, email domain, or organization name and the relevant results appear.
Internal users such as agents, light agents, and administrators can be followers.

* To quickly add yourself as a follower, click follow.



## To remove a follower from the ticket interface
* Click the delete button (X) in the person's name box in the Followers list


* To quickly remove yourself as a follower, click unfollow.

* To add agents as followers from ticket notifications

* From your email client, open the ticket notification.
* Open the CC line, per your email provider’s instructions.
* Add the name of the user you want to add as a follower. Repeat as necessary.
* Add your comment and send the email.



# 2.3.1
- changed tiledesk logo for emails
- open modules: analytics, activity log, multi tenancy, departments, groups, canned responses, tags, triggers, webhooks

# 2.2.39
- Added enterprise module
- Log fix
- Added DISABLE_MONGO_PASSWORD_MASK env variable
- Embedded rasa  proxy
- Added Swedish, Uzbek and Kazakh languages
- Added Azerbaijani language

# 2.2.38  
- Unlocked departments, groups, multi-tenant, tags and canned resposes modules

# 2.2.37  -> PROD 
- skip subtype private message for notification

# 2.2.36
- Ukraine translations

# 2.2.35 
- BugFix projection for /me service

# 2.2.34  
- Added transcript webpage for users without system messages

# 2.2.33
- Request fulltext sort fix

# 2.2.32
- Added Arabic language for the widget
- Updated dependencies with npm update
- Filter requests by lead email

# 2.2.31 (compatible with: Dashboard 2.2.37, Widget 5.0.25)
- Fix email template reading from project.
- Fix export messages to csv
- Fix ip address resolver
- Exclude poweredBy field from widget endpoint
- Bugfix when a conversation has a first_text with \agent
- Added rasa chatbot chatbot type
- Added visitor email and fullname in the fulltext index 

# 2.2.30
- Log fix

# 2.2.29 
- Added endpoint to find requests created by users and guests
- Log fix

# 2.2.28 (compatible with dasboard ver. 2.2.36)
- Operator.select returns context object that contains the temp request
- Added Serbian language to the widget
- Added tag field to the project_user
- Removed default BCC from email
- BugFix: Avoid cluster concurrent jobs in multiple nodes
- Faq template now support blank and example
- Organizzation support added
- ipFilter related to the project is now supported
- Added filter channel name for the request
- Added edit card for payment
- Fix concierge concierge bot for department selection
- Added filter to find a request by ticket_id
- Added filter to snap_lead_lead_id for request
- Added endpoint to close a request by guest


# 2.2.26 (compatible with dasboard ver. 2.2.35)
- Tag fix for 2.2.25

# 2.2.25
- New label prechat form
- Updated mongodb-runner from 4.8.1 to 4.8.3 to fix ssh key error

# 2.2.24 
- webhook subscription can fetch temmates endpoint
- Added hasBot and createdAt index to the request model

# 2.2.23 
- Increased list answers limit from 1000 to 3000

# 2.2.22
- Increased list answers limit from 300 to 1000

# 2.2.21 
- Increased list answers limit from 100 to 300
- enabled again waiting time in widgets endpoint unused


# 2.2.20
- disabled waiting time in widgets endpoint unused

# 2.2.18
- Router logger module enable with ROUTELOGGER_ENABLED=true

# 2.2.17
- Removed default fallback limit on parse reply

# 2.2.16 
- Email templates endpoint
- Created request.updated event for request event and deprecated request.update.comment
- Added Handlebars template processor for the message transformer module only if message.attributes.templateProcessor=true
- Email test send endpoint
- Bugfix widget label
- Added /intents alias for /faq endpoint
- The request_id field of the request model has now a unique index

# 2.2.15
- Added catch messageService.send for bot
- Added external searcher for bot( ex. Rasa proxy) 
- Faq language fix taken from bot language for create single and import from csv
- Lower case reset password fix
- Added alias /bots for /faq_kb

# 2.2.14
- Fix Tiledesk Queue 1.1.11 with authEvent.queueEnabled = true 

# 2.2.13
- Send message validation with empty text

# 2.2.12
- Add /bot endpoint
- Bot and subscription can manage bots

# 2.2.11 
- Logfix

# 2.2.10
- Native mqtt auth fix

# 2.2.8
- Public trigger module

# 2.2.6
- Quota license fix

# 2.2.4  -> PROD
- email invitation fix

# 2.2.3
- Email inboud fix (others disabled and inboudDomain variable fix and token query string encode fix)

# 2.2.2
- log fix

# 2.2.1
- log fix

# 2.2.0
- Cache circleci fix
- Added EMAIL_REPLY_ENABLED and EMAIL_INBOUND_DOMAIN env parameters.
- Added API_URL env variable TODO use wehbook url the same as API_URL if not differnet

# 2.1.42 (Compatible with tiledesk-dashboard 2.2.X)


- Added ticket_id sequence field to the request model
- Routing round robin fix (Also in 2.1.40.1)
- GLOBAL_SECRET env variable fix (Also in 2.1.40.4)
- Chatbot now support blocked_intent
- BugFix route request to another department with the same agents (Also in 2.1.40.1)
- Renamed the chatbot webhook payload field from faq to intent
- Updated tiledesk-chat-util to 0.8.21  (Also in 2.1.40.1)
- Removed request first_text replace new line with empty string (for ticketing)

- Fix login problem when email contains upper case char
- Removed answer field from the fulltext search of the faqs (2.1.40.3)

- Stripe fix for adding new agents (2.1.40.13)
- Added request delete endpoint by id (Also in 2.1.40.15)
- Campaigns direct and for group (Also in 2.1.40.16)

- Csv request export added tags (2.1.40.14)
- Changed request_id to the new standard: support-group-<project_id>-<uid>
- Added tag to the department model
- Bugfix first message with an image fix and touchText limited to 30 character or subject (2.1.40.3)
- Fix request create if department id is not correct
- MessageRoot endpoint also for group messages (Also in 2.1.40.16)
- c21 handler group mesages support (Also in 2.1.40.16)
- Added recipientFullname field to message model. Added save method to messageServive (Also in 2.1.40.16)
- ChatBot webhook fix when the webhook returns also attributes 
- Messages export csv supported
- Request util to lookup id_project from request_id (2.1.40.24)

- Find user id from user email endpoint (also in 2.1.40.21)
- Inizialize enterprise modules before public modules
- Request Notification fix loading snapshot agents (also in 2.1.40.22)
- Config secret fix from env (also in 2.1.40.22)
- Lic ck for users (also in 2.1.40.26)
- Added s_ticketing_taking_01 trigger
- Added email template from project settings
- Faq pagination support
- For Ticketing send to the cc(s) the agent replies 
- \agent now is hidden
- added \close faq
- \close now is hidden
- set custom role in custom auth using signInWithCustomToken
- Chat21 contacts find for agent logged with custom auth 
- Added language field to faq_kb and used to specify the language for faq full-text query (default en)
- Added request priority field
- Concierge bot fix to reroute only for temp conversation without a bot. Race condition issue when you try manually route a request for example inside a chatbot webhook (Also in 2.1.40.31)
- Added webhook_enabled parameter to the faqService create method and test refactor
- Added SYNC_JOIN_LEAVE_GROUP_EVENT environment variable to enable sync between Chat21 (join and leave) and Tiledesk. Default is false. (Also in 2.1.40.32 )
- Added ALLOW_REOPEN_CHAT environment variable to reopen a chat if a user write after a chat is closed  
- Used message.received instead message.create in the messageActionsInterceptor to fix race condition sometime occurs with \close message sent by the bot      
- Please type your reply above this line Only if replyTo is specified
- Webhook origin header fix for webhook


## Email inbound
- EmailService supports custom email config with custom SMTP server settings and custom from email
- Added Tiledesk customer header in the outbound email
- Added Message-ID and sender (message sender fullname) on the outbound email 
- Added project object to sendRequestTranscript function
- Welcome label fix key
- Seamless source page fix

# 2.1.41 
- remove duplicate request script with: 1619185894304-request-remove-duplicated-request-by-request_id--autosync.js
- requestNotification improvement not sending email with empty email field
- Enabled witb DISABLE_SEND_OFFLINE_EMAIL the seamless conversation with email
- signinWithCustomToken endpoint of the auth router now support id_project body parameter for jwt with generic https://www.tiledesk.com audience field (used by tiledesk-smtp-server)
- files download endpoint
- emailService added EMAIL_REPLY_TO parameter;
- Added email notification for new message and new request for email and form channel (ticket) 
- Added microLanguageTransformationInterceptor enabled when message.attributes.microlanguage==true

# 2.1.40.35
- Quota license fix

# 2.1.40.34
- logfix

# 2.1.40.33

- Added setTimeout to resolve race condition for \close event returned by bot 

# 2.1.40.32
- Added SYNC_JOIN_LEAVE_GROUP_EVENT environment variable to enable sync between Chat21 (join and leave) and Tiledesk. Default is false. 

# 2.1.40.31
- Concierge bot fix to reroute only for temp conversation without a bot. Race condition issue when you try manually route a request for example inside a chatbot webhook 

# 2.1.40.30
- logfix

# 2.1.40.29
- --production for npm install within Docker for Enterprise

# 2.1.40.28
- --production for npm install within Docker

# 2.1.40.27
- Added language field to faq_kb and used to specify the language for faq full-text query 

# 2.1.40.26
- Lic ck bug fix for users

# 2.1.40.25 
- microLanguageTransformerInterceptor startup error. It is disabled. Module not present

# 2.1.40.24
- emailService toJSON is not a function fix

# 2.1.40.23
- requestNotification fix and requestUtilRoot lookup endpoint added

# 2.1.40.22
- Inizialize enterprise modules before public modules
- Request Notification fix loading snapshot agents

# 2.1.40.21
- Find user id from user email endpoint

# 2.1.40.20
- MessageRoot endpoint validation fix

# 2.1.40.19 
- Stripe fix with version 1.1.5

# 2.1.40.18
- Messages export csv supported

# 2.1.40.17
- Stripe restored to the previous version 1.1.3

# 2.1.40.16
- added message recipientfullname field to the message entity  to support chat campaigns for direct and group 

# 2.1.40.15
- tag fix

# 2.1.40.14
- requet CSV export fix

# 2.1.40.13 
- Stripe fix for adding new agents

# 2.1.40.12
- Docker image number fix 

# 2.1.40.11
- Docker image number fix 

# 2.1.40.10 
- Docker image number fix 

# 2.1.40.9 
- Added request delete endpoint by id

# 2.1.40.7
- logfix

# 2.1.40.6
- Fix request create if department id is not correct

# 2.1.40.5
- Logfix

# 2.1.40.4
- GLOBAL_SECRET env variable fix

# 2.1.40.3 
- Bugfix first message with an image fix and touchText limited to 30 character or subject
- Removed answer field from the fulltext search of the faqs


# 2.1.40.2 
- log fix

# 2.1.40.1
- Routing round robin fix
- Updated tiledesk-chat-util to 0.8.21
- BugFix route request to another department with the same agents


# 2.1.40
- webhook fix for return empty body
- log fix
- Added hasbot filter for GET requests endpoint

# 2.1.39
- Log fix
- Chat21 presence  webhook error handler fix 

# 2.1.38 -> TILEDESK DASHBOARD v2.1.49.1
- Added PUT /images/users endpoint where you can archive user's image at the root level of the path
- Added DELETE /images/users endpoint to delete an image
- Added PUT /images/users/photo endpoint where you can archive user's avatar at the root level of the path
- Root messages endpoint fixed
- Now RestHook module is public
- Minor deps update

# 2.1.37 

- Trigger module moved to public npm
- Websocket Message limit with environment variable WS_HISTORY_MESSAGES_LIMIT (300 default)
- Chat21Webhook now support then new event_type with values "conversation-archived" and the old "deleted-conversation" (back compatibility). The field recipient_id is renamed to convers_with, but maintained for back compatibility. The project_id is taken from the support-group but also from conversation.attributes for back compatibility.
- Chat21 group sync now create group with group- prefix. If old group exists (before group- prefix) group updating and deleting can create a duplicate entries
- Fix intent_info object for is_fallback false
- Updated dependencies
- Updated snapshot.lead when a lead is updated. Editing a lead requires to refresh the realtime conversation panel.
- Lead PUT endpoint fix for status field
- Websocket error fix when MongoDB is < 4.XX catch snapshot exception
- Added request.attributes.abandoned_by_project_users when a user leave a chat
- Send messages direct endpoint
- Create project_user endpoint by agent (Ticketing) now is with Guest Role
- Bot webhook error now return the standard message and not the error 
- Added WEBHOOK_ORIGIN env variable for each webhook calls


# 2.1.36
- Minor log fix 

# 2.1.35
- Minor ws log fix 

# 2.1.34
- Minor ws log fix 

# 2.1.33
- Disabled sendUser email for requestNotification

# 2.1.32
- Project_user endpoint (/get) now can be obtained by uuid_user field
- Added intent_info to message.attributes sent from bot
- Pending invite email lowercase fix
- Project_user invite email lowercase fix
- Widget endpoint fix for not found project 
- Chat21 WebHook now support also "message-sent" event_type value

# 2.1.31
- Log fix

# 2.1.30 (Requires tiledesk-dashboard 2.1.45+)
- Added Pending Invitation db indexes
- The requests queries with status open (!=1000) are not limited for free accounts.   
- Performance improvment moving request.agents (select false) and availableAgentsCount fields into request.snapshot. Updated @tiledesk-ent/tiledesk-server-triggers:1.1.75. Migration scripts: 1616687831941-trigger_availableAgentsCount_to_snapshot_agents--autosync 1616687831941-trigger_availableAgentsCount_to_snapshot_agents-key--autosync 1616685902635-request_agents_to_snapshot_agents--autosync
- Added event index to increase analytics performance 
- Snapshot.department fix for ticketing (Without a selected department)  
- Websocket query is improved (without lead populate and with lean. it's enabled isAuthenticated field of the request.snapshot.requester)
- Added user object to the event emit method to fix decoded_jwt field of the lead and request
- The Request deletion also deletes messages    

# 2.1.21
- BugFix: request availableAgentsCount performance fix

# 2.1.20
- Default faq fix
- Log fix

# 2.1.19
- Default faq with actions example

# 2.1.18
- Added faq intent_display_name and intent_id. Included database migration script: 1615214914082-faq-intent_id-intent_display_name-fields-added--autosync.js
- Now you can filter the requests by snap_department_routing, req.query.snap_department_default, req.query.snap_department_id_bot, snap_department_id_bot_exists 
- BugFix: Parse FAQ form CSV fix
- You can enabled Smart Assignment by default for new project with SMART_ASSIGNMENT_CHAT_LIMIT_ON_DEFAULT_PROJECT=true (default = false)
- Added snapshot object to the request to store embedded snap objects like department, agents, lead, requester.
- Updated repository dependencies
- Websocket teammates update fix
- Now support node 12.x and docker node 12
- Updated chat21 dependencies (firebase, etc.)
- Added project_user creation endpoint for Ticketing (POST)
- Set participants endpoint supports no_populate query param
- Added request /assing endpoint
- Now a request can be assinged directly to a partipant without a department for ticket use case.
- Added Analytics events
- Added leads filter to retrieve only their with email (with_email=true) and with fullname (with_fullname=true)
- Now the bot can find actions answers by intent_display_name and intent_id
- Project_user deletion only for owner

# 2.1.17
- Log fix

# 2.1.16
- CSV export fix

# 2.1.15


- Automatically close unresponsive bot conversation  (also in 2.1.14.4). You may configure CLOSE_BOT_UNRESPONSIVE_REQUESTS_CRON_EXPRESSION, etc.
- Added _answerid field to the messages replies send from the chatbot under attributes field.
- Added fulltext indexes for request.notes request.tags and request.subject.. 
- Added default language field for pivoting. We disabled pre traslation for EN, IT, FR. So only pivot language is taken into account. Included database migration script:  1604082287722-labels-data-default-fields--autosync.js
- Added status field to project_user collection. Included database migration script: 1603797978971-project_users-status-field-added--autosync.js
- Added channel_type (group or direct) and channel (chat21, whatsapp, etc..) fields to the message model. Included database migration script: 1602847963299-message-channel_type-and-channel-fields-added--autosync.js
- Added posfix $reply_time to WAITING_TIME_FOUND label. Included database migration script: 1604082288723-labels-waiting_time-added_suffix_reply_time--autosync.js
- Added channelOutbound (chat21, whatsapp, etc..) fields to indentify the outboud channel to the request model. Included database migration script: 1603955232377-requests-channel-outbound-fields--autosync.js
- Renamed request field UNSERVED (100) to UNASSIGNED (100) and SERVED (200) to ASSIGNED (200)
- Renamed property max_agent_served_chat to max_agent_assigned_chat of the project.settings object.Included database migration script: project-settings-max_agent_assigned_renamed--autosync
- Renamed property max_served_chat to max_assigned_chat of the project_user object. Included database migration script: project_user-max_assigned_chat-renamed--autosync
- SourcePage and bot answer stats with tiledesk-ent/tiledesk-server-analytics: 1.1.9
- Fix Conversation export to CSV
- Bots statistics with tiledesk-ent/tiledesk-server-analytics: 1.1.8
- Added support to channel selection for resthook module @tiledesk-ent/tiledesk-server-resthook: 1.1.47
- Added supervisor role
- BugFix: Updated jwthistory and queue module with listen function fix (also in 2.1.14.1)
- Department patch method added (also in 2.1.14.2)
- BugFix: Now you can signupWithCustomToken using subject=userexternal and subscription type audience (https://tiledesk.com/subscriptions/subid). It's used by Whatsapp and Facebook Messenger apps (also in 2.1.14.2)
- Chat21 contact detail endpoint
- Added tags field for the lead model
- Added location field to the request model. Auto populate location field from ip
- Update all dependencies to last version
- Image endpoint now return also thumbnail filename
- Added analytics endpoint for messages (also in 2.1.14.2)
- New websocket return also events model (beta)
- Log fix for signup and signin endpoint (also in 2.1.14.2)
- Added email notification setting for each teammate (also in 2.1.14.3)
- Added email notification setting for each project
- Bugfix (@tiledesk-ent/tiledesk-server-triggers":1.1.69) for chatbot invitation race condition with Chat21 createGroup and setMembers method. Now the trigger listens to requestEvent.on("request.support_group.created",..) event and not to requestEvent.on("request.create",..) event. This doen't require data migration for old triggers (also in 2.1.14.4).
- Added plugin to save log to MongoDB (also in 2.1.14.4) with WRITE_LOG_TO_MONGODB=true, LOG_MONGODB_LEVEL, DATABASE_LOG_URI
- Added plugin to save multi-tenant log to MongoDB with WRITE_LOG_MT_TO_MONGODB=true, LOG_MT_MONGODB_LEVEL
- Added DEFAULT_FULLTEXT_INDEX_LANGUAGE env parameter for Faq, Lead, message and requests. Before the index language was Italian.
- Added support to \close action
- Disabled send trascript email for autoclosed conversations #413

# 2.1.14.5 -> Cloud Production
- Winston MongoDB Log fix

# 2.1.14.4
- Automatically close unresponsive bot conversation. You may configure CLOSE_BOT_UNRESPONSIVE_REQUESTS_CRON_EXPRESSION, etc.
- Bugfix (@tiledesk-ent/tiledesk-server-triggers":1.1.69) for chatbot invitation race condition with Chat21 createGroup and setMembers method. Now the trigger listens to requestEvent.on("request.support_group.created",..) event and not to requestEvent.on("request.create",..) event. This doen't require data migration for old triggers.
- Added plugin to save log to MongoDB 

# 2.1.14.3
Added email notification setting for each teammate (also in 2.1.14.3)

# 2.1.14.2
- Department patch method added (ok)
- BugFix: Now you can signupWithCustomToken using subject=userexternal and subscription type audience (https://tiledesk.com/subscriptions/subid). It's used by Whatsapp and Facebook Messenger apps . UNISALENTO TEST
- Added analytics endpoint for messages 
- Log fix for signup and signin endpoint.

# 2.1.14.1
- Updated queue module with listen function fix

# 2.1.14
- Renamed field presence.lastOnlineAt to changedAt
- Added filter presencestatus for project_users endpoint
- Websocket events updated with filters

# 2.1.13
- Added Cors options pre-flight with 
- Fix TD218 Audit log for user invitation already registered

# 2.1.12
- Chat21 engine selection with CHAT21_ENGINE=[mqtt|firebase]
- Schema migration tool with mongoose-migrate. Added env property DISABLE_AUTO_SHEMA_MIGRATION
- #TD-250 Added emoji callout
- Cors fix. Removed alternative cors response header.
- Channel manager refactoring
- Event route fix and ws event endpoint
- WS projectì_user endpoint is now usable by other teammate
- Message text required only for type text messages
- #TD-251 Email lower case fix
- Updated Node default engine to 11.15.0
- Added hasBot query filter to retrive conversations with or without a bot
- Chatbot \frame action command supported
- Supported components: tiledesk-dashboard:2.0.73+ chat21-webwidget:4.0.75+ chat21-ionic:2.0.12

# 2.1.11
- Mongo support for Winston with: WRITE_LOG_TO_MONGODB=true 
- Logfix

# 2.1.10
- Tiledesk Chat21 groups syncronizer. Enable with SYNC_CHAT21_GROUPS="true"
- Built-in faq updated  and chatbot webhook example changed
- Return role: admin if the admin sign-in with email and password
- Default fallback event emitting
- Files and images storage services
- Supported components: tiledesk-dashboard:2.0.70 chat21-webwidget:4.0.73 chat21-ionic:2.0.12

# 2.1.9
- Widget departments fix

# 2.1.8
- SigninWithCustomToken fix for different audience types

# 2.1.7
- Minor fix

# 2.1.6 
- Push notification fix for the first support message and for group joining.

# 2.1.5
- Email template externalization with handlebars under /template/email folder. 
 You can override the email template using EMAIL_ASSIGN_REQUEST_HTML_TEMPLATE, EMAIL_POOLED_REQUEST_HTML_TEMPLATE,
 EMAIL_RESET_PASSWORD_HTML_TEMPLATE, EMAIL_PASSWORD_CHANGED_HTML_TEMPLATE, EMAIL_EXUSER_INVITED_HTML_TEMPLATE,
 EMAIL_NEWUSER_INVITED_HTML_TEMPLATE, EMAIL_VERIFY_HTML_TEMPLATE, EMAIL_SEND_TRANSCRIPT_HTML_TEMPLATE env variables

# 2.1.4
- Email config parameters fix

# 2.1.3
- Language pivot fix

# 2.1.2
- License fix

# 2.1.1
- Minor bug fix

# 2.1.0
- Release

# 2.1.0-beta24
- Minor bug fix

# 2.1.0-beta23
- Email fix for pooled request caming from bot

# 2.1.0-beta22 
- filter identity bot. use all=true to get all bot. Require Dashboard 2.0.49
- Chat21 Contacts for web chat recipients list
- Chat21 Presence webhook 
- Widget i8n


# 2.1.0-beta14 # 2.1.0-beta15 # 2.1.0-beta16 # 2.1.0-beta..20
- Docker tag

# 2.1.0-beta13
- add canned responses and tag library
- add groups and departments modules
- cache, routing, resthook deps update
- added request.status all for rest api
- request and lead physical deletion
- add mt and visitor counter modules
- removed reqLog feature
- typing webhook fix
- label fix with no pivoting to default languages
- removed terminus

# 2.1.0-beta12
- Send transcript fix

# 2.1.0-beta11
- Activity archiver fix for preflight request
- request physical deletion

# 2.1.0-beta10
- Minor bugfix for events

# 2.1.0-beta9
- Trigger fix for custom authentication

# 2.1.0-beta7
- Activities, Jwt History and Rest Hook update

# 2.1.0-beta6
- Widget language pivoting fix

# 2.1.0-beta5
- Support for cache
- Removed message_count of request model
- Concierge bot now update lead after preflight

# 2.1.0-beta4
- added isAuthenticated field to project_user model
- trigger update 
- set requester to request create of the chat21 webhook

# 2.1.0-beta2
- added field participantsAgents, participantsBot and hasBot fields. Migration file updated
- request, department, project, project_users, subscription indexes added
- conciergeBot multilanguage improvement
- trigger module updated
- resthook module updated with minor fixes
- added fanout pub sub to queue module and added support for *.queue.pubsub events
- WS_HISTORY_REQUESTS_LIMIT env variable added

# 2.1.0-beta1 
- project rest api order fix by updatedAt
- improved internal bot with defaultFallback and smart text and webhook
- messageService.send with metadata field
- widget and test page route
- Dockerfile fix (removed nano and nodemon)
- invite teammate rest api with available or unavailable 
- welcome message when a request is assigned to an agent (TOUCHING_OPERATOR)
- added department description and bot description fields
- added request rating rest api (PATCH)
- implemented \start command 
- trigger improvement 
- added request status (TEMP=50) and preflight field (exclude request.preflight=true from ws) 
- concierge bot now supports switch from preflight to standard request (rerouting, preflight update, first_text update) 
- added typing event 
- created Message Transformation Engine for multilanguage message (labels with the new labelService) and text templating
- added language field to message model and indexes improvement
- tag and tagLibrary refactoring 
- resigninAnonymously rest api for widget re-authenticate
- email notification improvement for agent joing 
- added label for office closed
- added queue for Enterprise version (websocket)
- Websocket pub/sub fix with handlePublishMessageToClientId
- Websocket performance fix with lean and removing populate 
- Added kubernetes sample config file
- Added required firstname and lastname to signup endpoint
- Removed message.request.messages and message.request.department.bot for message.create event  

================================================
FILE: Dockerfile
================================================
FROM node:18-bullseye

RUN sed -i 's/stable\/updates/stable-security\/updates/' /etc/apt/sources.list


RUN apt-get update

# Create app directory
WORKDIR /usr/src/app

ARG NPM_TOKEN

RUN if [ "$NPM_TOKEN" ]; \
    then RUN COPY .npmrc_ .npmrc \
    else export SOMEVAR=world; \
    fi


# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

RUN npm install --production

RUN rm -f .npmrc

# Bundle app source
COPY . .

EXPOSE 3000

CMD [ "npm", "start" ]



================================================
FILE: Dockerfile-en
================================================
FROM node:18-bullseye

RUN sed -i 's/stable\/updates/stable-security\/updates/' /etc/apt/sources.list

RUN apt-get update

# Create app directory
WORKDIR /usr/src/app

# Accept build arguments
ARG NPM_TOKEN
ARG VOICE_TOKEN
ARG VOICE_TWILIO_TOKEN

COPY .npmrc_ .npmrc

# Set environment variable based on build argument
ENV VOICE_TOKEN=${VOICE_TOKEN}
ENV VOICE_TWILIO_TOKEN=${VOICE_TWILIO_TOKEN}

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

RUN npm install --production

RUN rm -f .npmrc

# Bundle app source
COPY . .

EXPOSE 3000

CMD [ "npm", "start" ]



================================================
FILE: Dockerfile-jobs
================================================
FROM node:18-bullseye

RUN sed -i 's/stable\/updates/stable-security\/updates/' /etc/apt/sources.list

RUN apt-get update

# Create app directory
WORKDIR /usr/src/app

ARG NPM_TOKEN

RUN if [ "$NPM_TOKEN" ]; \
    then RUN COPY .npmrc_ .npmrc \
    else export SOMEVAR=world; \
    fi


# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

RUN npm install --production

RUN rm -f .npmrc

# Bundle app source
COPY . .

EXPOSE 3000

CMD [ "node", "jobs.js" ]



================================================
FILE: Dockerfile-profiler
================================================
FROM node:18-bullseye

RUN sed -i 's/stable\/updates/stable-security\/updates/' /etc/apt/sources.list


RUN apt-get update

# Create app directory
WORKDIR /usr/src/app

ARG NPM_TOKEN

RUN if [ "$NPM_TOKEN" ]; \
    then RUN COPY .npmrc_ .npmrc \
    else export SOMEVAR=world; \
    fi


# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

RUN npm install --production

RUN rm -f .npmrc

# Bundle app source
COPY . .

EXPOSE 3000

CMD [ "node", "--prof", "./bin/www" ]


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 Tiledesk

Permission is hereby granted, free of charge, 
to any person obtaining a copy of this software and associated documentation files (the "Software"), 
to deal in the Software without restriction, including without limitation the rights to use, 
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. 
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
[![npm version](https://badge.fury.io/js/%40tiledesk%2Ftiledesk-server.svg)](https://badge.fury.io/js/%40tiledesk%2Ftiledesk-server)

[![CircleCI](https://circleci.com/gh/Tiledesk/tiledesk-server.svg?style=svg)](https://circleci.com/gh/Tiledesk/tiledesk-server)


> ***🚀 Do you want to install Tiledesk on your server with just one click?***
> 
> ***Use [Docker Compose Tiledesk installation](https://github.com/Tiledesk/tiledesk-deployment/blob/master/docker-compose/README.md) guide***

# Introduction

Tiledesk-server is the server engine of Tiledesk. Tiledesk is an Open Source Live Chat platform with integrated Chatbots written in NodeJs and Express. Build your own customer support with a multi-channel platform for Web, Android and iOS. 

Designed to be open source since the beginning, we actively worked on it to create a totally new, first class customer service platform based on instant messaging.

What is Tiledesk today? It became the open source “conversational app development” platform that everyone needs 😌

You can use Tiledesk to increase sales for your website or for post-sales customer service. Every conversation can be automated using our first class native chatbot technology.
You can also connect your own applications using our APIs or Webhooks.
Moreover you can deploy entire visual applications inside a conversation. And your applications can converse with your chatbots or your end-users! We know this is cool 😎

Tiledesk is multichannel in a totally new way. You can write your chatbot scripts with images, buttons and other cool elements that your channels support. But you will configureyour chatbot replies only once. They will run on every channel, auto-adapting the responses to the target channel whatever it is, Whatsapp, Facebook Messenger, Telegram etc.

More info on Tiledesk website: https://www.tiledesk.com.

You can find technical documentation here: https://developer.tiledesk.com

# Prerequisites for Installation

* [Nodejs](https://www.npmjs.com/) and npm installed. Suggested versions are NodeJS 12.20.2 and NPM 6.14.11 
* [MongoDb](https://www.mongodb.com) installed

# Run Tiledesk with Docker Compose

Do you want to install all the Tiledesk components on your server with just one click?
Use [Docker Compose Tiledesk installation guide](https://github.com/Tiledesk/tiledesk-deployment/blob/master/docker-compose/README.md)

# Running Tiledesk Server

## Using Docker


### Configure .env file 
```
curl https://raw.githubusercontent.com/Tiledesk/tiledesk-server/master/.env.sample --output .env
nano .env #configure .env file properly
```

### Running
If you want to run tiledesk and mongo with docker run :

```
docker run --name tiledesk-mongo -d mongo
docker run -p 3000:3000 --env DATABASE_URI="mongodb://mongo/tiledesk-server" --env-file .env --link tiledesk-mongo:mongo tiledesk/tiledesk-server
```

Otherwise if you want to run tiledesk only with docker run :

```
docker run -p 3000:3000 --env DATABASE_URI="mongodb://YOUR_MONGO_INSTALLATION_ENDPOINT/tiledesk-server" --env-file .env tiledesk/tiledesk-server
```



## Run locally with npm

Steps to run with npm:

```
npm install -g @tiledesk/tiledesk-server mongodb-runner
mongodb-runner start
curl https://raw.githubusercontent.com/Tiledesk/tiledesk-server/master/.env.sample --output .env
nano .env #configure .env file properly
tiledesk-server  
```

If you want to load .env file from another path: `DOTENV_PATH=/MY/ABSOLUTE/PATH/.env tiledesk-server`

Note: If installation with -g fails due to permission problems (npm ERR! code 'EACCES'), please refer to this [link](https://docs.npmjs.com/getting-started/fixing-npm-permissions).

## Install from source code

* Clone this repo
* Install dependencies with `npm install`
* Configure the tiledesk .env file. You can see an example in the file .env.sample under the root folder. Rename it from .env.sample to .env and configure it properly. 
* Run the app with the command `npm start`.


## Deploy on Heroku

Deploy with button:

[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/Tiledesk/tiledesk-server)

# Community? Questions? Support ?
If you need help or just want to hang out, come, say hi on our [<img width="15" alt="Tiledesk discord" src="https://seeklogo.com/images/D/discord-color-logo-E5E6DFEF80-seeklogo.com.png"> Discord](https://discord.gg/Q5A6Ewadmz) server.

# REST API

See the Tiledesk REST API [here](https://developer.tiledesk.com/apis/rest-api/introduction)

# Upgrading 
To see how to upgrade tiledesk-server see [here](./docs/upgrading.md) 

# Testing
Run unit test with `npm test` and integration test with `npm run test:int` 




================================================
FILE: app.js
================================================
var dotenvPath = undefined;

if (process.env.DOTENV_PATH) {
  dotenvPath = process.env.DOTENV_PATH;
  console.log("load dotenv form DOTENV_PATH", dotenvPath);
}

if (process.env.LOAD_DOTENV_SUBFOLDER ) {
  console.log("load dotenv form LOAD_DOTENV_SUBFOLDER");
  dotenvPath = __dirname+'/confenv/.env';
}

require('dotenv').config({ path: dotenvPath});


var express = require('express');
var path = require('path');
// var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var morgan = require('morgan');
var mongoose = require('mongoose');

var passport = require('passport');
require('./middleware/passport')(passport);

var config = require('./config/database');
var cors = require('cors');
var Project = require("./models/project");
var validtoken = require('./middleware/valid-token');
var roleChecker = require('./middleware/has-role');

const MaskData = require("maskdata");
var winston = require('./config/winston');


// DATABASE CONNECTION

// https://bretkikehara.wordpress.com/2013/05/02/nodejs-creating-your-first-global-module/
var databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI || config.database;

if (!databaseUri) { //TODO??
  winston.warn('DATABASE_URI not specified, falling back to localhost.');
}

if (process.env.NODE_ENV == 'test')  {
  databaseUri = config.databasetest;
}

const masked_databaseUri = MaskData.maskPhone(databaseUri, {
        maskWith : "*",
        unmaskedStartDigits: 15, 
        unmaskedEndDigits: 5
      });

if (process.env.DISABLE_MONGO_PASSWORD_MASK ==true || process.env.DISABLE_MONGO_PASSWORD_MASK == "true")  {
  winston.info("DatabaseUri: " + databaseUri);
}else {
  winston.info("DatabaseUri masked: " + masked_databaseUri);
}


var autoIndex = true;
if (process.env.MONGOOSE_AUTOINDEX) {
  autoIndex = process.env.MONGOOSE_AUTOINDEX;
}
winston.info("DB AutoIndex: " + autoIndex);

let useUnifiedTopology = process.env.MONGOOSE_UNIFIED_TOPOLOGY === 'true';
winston.info("DB useUnifiedTopology: ", useUnifiedTopology, typeof useUnifiedTopology);

var connection = mongoose.connect(databaseUri, { "useNewUrlParser": true, "autoIndex": autoIndex, "useUnifiedTopology": useUnifiedTopology }, function(err) {
  if (err) { 
    winston.error('Failed to connect to MongoDB on ' + databaseUri + " ", err);
    process.exit(1);
  }
  winston.info("Mongoose connection done on host: "+mongoose.connection.host + " on port: " + mongoose.connection.port + " with name: "+ mongoose.connection.name)// , mongoose.connection.db);
});
if (process.env.MONGOOSE_DEBUG==="true") {
  mongoose.set('debug', true);
}
mongoose.set('useFindAndModify', false); // https://mongoosejs.com/docs/deprecations.html#-findandmodify-
mongoose.set('useCreateIndex', true);
//mongoose.set('useUnifiedTopology', false); 

// CONNECT REDIS - CHECK IT
const { TdCache } = require('./utils/TdCache');
let tdCache = new TdCache({
    host: process.env.CACHE_REDIS_HOST,
    port: process.env.CACHE_REDIS_PORT,
    password: process.env.CACHE_REDIS_PASSWORD
});

tdCache.connect();

// ROUTES DECLARATION
var troubleshooting = require('./routes/troubleshooting');
var auth = require('./routes/auth');
var authtest = require('./routes/authtest');
var authtestWithRoleCheck = require('./routes/authtestWithRoleCheck');

var lead = require('./routes/lead');
var message = require('./routes/message');
var messagesRootRoute = require('./routes/messagesRoot');
var department = require('./routes/department');
var group = require('./routes/group');
var resthook = require('./routes/subscription');
var tag = require('./routes/tag');
var faq = require('./routes/faq');
var faq_kb = require('./routes/faq_kb');
var project = require('./routes/project');
var project_user = require('./routes/project_user');
var project_users_test = require('./routes/project_user_test');
var request = require('./routes/request');
// var setting = require('./routes/setting');
var users = require('./routes/users');
var usersUtil = require('./routes/users-util');
var publicRequest = require('./routes/public-request');
var userRequest = require('./routes/user-request');
var publicAnalytics = require('./routes/public-analytics');
var pendinginvitation = require('./routes/pending-invitation');
var jwtroute = require('./routes/jwt');
var key = require('./routes/key');
var widgets = require('./routes/widget');
var widgetsLoader = require('./routes/widgetLoader');
var openai = require('./routes/openai');
var llm = require('./routes/llm');
var quotes = require('./routes/quotes');
var integration = require('./routes/integration')
var kbsettings = require('./routes/kbsettings');
var kb = require('./routes/kb');
var unanswered = require('./routes/unanswered');
var answered = require('./routes/answered');

// var admin = require('./routes/admin');
var faqpub = require('./routes/faqpub');
var labels = require('./routes/labels');
var fetchLabels = require('./middleware/fetchLabels');
var cacheUtil = require("./utils/cacheUtil");
var orgUtil = require("./utils/orgUtil");
var images = require('./routes/images');
var files = require('./routes/files');
let filesp = require('./routes/filesp');
var campaigns = require('./routes/campaigns');
var logs = require('./routes/logs');
var requestUtilRoot = require('./routes/requestUtilRoot');
var urls = require('./routes/urls');
var email = require('./routes/email');
var property = require('./routes/property');
var segment = require('./routes/segment');
var webhook = require('./routes/webhook');
var webhooks = require('./routes/webhooks');
var roles = require('./routes/roles');
var copilot = require('./routes/copilot');
var mcp = require('./routes/mcp');

var bootDataLoader = require('./services/bootDataLoader');
var settingDataLoader = require('./services/settingDataLoader');
var schemaMigrationService = require('./services/schemaMigrationService');
var RouterLogger = require('./models/routerLogger');
var cacheEnabler = require("./services/cacheEnabler");
const session = require('express-session');
const RedisStore = require("connect-redis").default
const botEvent = require('./event/botEvent');

require('./services/mongoose-cache-fn')(mongoose);


var subscriptionNotifier = require('./services/subscriptionNotifier');
subscriptionNotifier.start();

var subscriptionNotifierQueued = require('./services/subscriptionNotifierQueued');


var botSubscriptionNotifier = require('./services/BotSubscriptionNotifier');
botSubscriptionNotifier.start(); //queued but disabled

botEvent.listen(); //queued but disabled

var trainingService = require('./services/trainingService');
trainingService.start();

// job_here

var geoService = require('./services/geoService');
// geoService.listen(); //queued

var updateLeadQueued = require('./services/updateLeadQueued');
var updateRequestSnapshotQueued = require('./services/updateRequestSnapshotQueued');

let JobsManager = require('./jobsManager');

let jobWorkerEnabled = false;
if (process.env.JOB_WORKER_ENABLED=="true" || process.env.JOB_WORKER_ENABLED == true) {
    jobWorkerEnabled = true;
}
winston.info("JobsManager jobWorkerEnabled: "+ jobWorkerEnabled);  

let jobsManager = new JobsManager(jobWorkerEnabled, geoService, botEvent, subscriptionNotifierQueued, botSubscriptionNotifier, updateLeadQueued, updateRequestSnapshotQueued);

var faqBotHandler = require('./services/faqBotHandler');
faqBotHandler.listen();

var pubModulesManager = require('./pubmodules/pubModulesManager');
pubModulesManager.init({express:express, mongoose:mongoose, passport:passport, databaseUri:databaseUri, routes:{}, jobsManager:jobsManager, tdCache:tdCache});
  
jobsManager.listen(); //listen after pubmodules to enabled queued *.queueEnabled events

let whatsappQueue = require('@tiledesk/tiledesk-whatsapp-jobworker');
winston.info("whatsappQueue");
jobsManager.listenWhatsappQueue(whatsappQueue);

let multiWorkerQueue = require('@tiledesk/tiledesk-multi-worker');
winston.info("multiWorkerQueue from App")
jobsManager.listenMultiWorker(multiWorkerQueue);

var channelManager = require('./channels/channelManager');
channelManager.listen(); 

var IPFilter = require('./middleware/ipFilter');

// job_here
var BanUserNotifier = require('./services/banUserNotifier');
BanUserNotifier.listen();
const { ChatbotService } = require('./services/chatbotService');
const { QuoteManager } = require('./services/QuoteManager');
const RateManager = require('./services/RateManager');

let qm = new QuoteManager({ tdCache: tdCache });
qm.start();

let rm = new RateManager({ tdCache: tdCache });


var modulesManager = undefined;
try {
  modulesManager = require('./services/modulesManager');
  modulesManager.init({express:express, mongoose:mongoose, passport:passport, routes: {departmentsRoute: department, projectsRoute: project, widgetsRoute: widgets} });
} catch(err) {
  winston.info("ModulesManager not present");
}


//enterprise modules can modify pubmodule
modulesManager.start();

pubModulesManager.start();


settingDataLoader.save();
schemaMigrationService.checkSchemaMigration();

if (process.env.CREATE_INITIAL_DATA !== "false") {
   bootDataLoader.create();
}




var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.set('chatbot_service', new ChatbotService())
app.set('redis_client', tdCache);
app.set('quote_manager', qm);
app.set('rate_manager', rm);

// TODO DELETE IT IN THE NEXT RELEASE
if (process.env.ENABLE_ALTERNATIVE_CORS_MIDDLEWARE === "true") {  
  app.use(function (req, res, next) {
    res.header("Access-Control-Allow-Origin", "*"); //qui dice cequens attento
    // var request_cors_header = req.headers[""]
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, x-xsrf-token");
    res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
    next();
  });

  winston.info("Enabled alternative cors middleware");
} else {
  winston.info("Used standard cors middleware");
}


// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
// app.use(morgan('dev'));
// app.use(morgan('combined'));


// app.use(bodyParser.json());

// https://stackoverflow.com/questions/18710225/node-js-get-raw-request-body-using-express


const JSON_BODY_LIMIT = process.env.JSON_BODY_LIMIT || '500KB';
winston.debug("JSON_BODY_LIMIT : " + JSON_BODY_LIMIT);

const WEBHOOK_BODY_LIMIT = process.env.WEBHOOK_BODY_LIMIT || '5mb';
winston.debug("WEBHOOK_BODY_LIMIT : " + WEBHOOK_BODY_LIMIT);

const webhookParser = bodyParser.json({ limit: WEBHOOK_BODY_LIMIT });

app.use('/webhook', webhookParser, webhook);

app.use(bodyParser.json({limit: JSON_BODY_LIMIT,
  verify: function (req, res, buf) {
    // var url = req.originalUrl;
    // if (url.indexOf('/stripe/')) {
      req.rawBody = buf.toString();
      winston.debug("bodyParser verify stripe", req.rawBody);
    // } 
  }
}));

app.use(bodyParser.urlencoded({limit: JSON_BODY_LIMIT, extended: true }));

app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// app.use('/uploads', express.static(path.join(__dirname, 'uploads')));

//app.use(morgan('dev'));

if (process.env.ENABLE_ACCESSLOG) {
  app.use(morgan('combined', { stream: winston.stream }));
}

app.use(passport.initialize());
// If deployed behind a proxy/ingress (TLS terminated upstream), enable this
// if (process.env.TRUST_PROXY === "true") {
app.set('trust proxy', 1);
// }

// After you declare "app"
if (process.env.DISABLE_SESSION_STRATEGY==true ||  process.env.DISABLE_SESSION_STRATEGY=="true" ) {
  winston.info("Express Session disabled");
} else {

  // https://www.npmjs.com/package/express-session
  let sessionSecret = process.env.SESSION_SECRET || "tiledesk-session-secret";

  if (process.env.ENABLE_REDIS_SESSION==true ||  process.env.ENABLE_REDIS_SESSION=="true" ) {
  
      console.log("Starting redis...") // errors occurs
      // Initialize client.
      // let redisClient = createClient()
      // redisClient.connect().catch(console.error)

      let cacheClient = undefined;
      if (pubModulesManager.cache) {
        cacheClient = pubModulesManager.cache._cache._cache;  //_cache._cache to jump directly to redis modules without cacheoose wrapper (don't support await)
      }
      // winston.info("Express Session cacheClient",cacheClient);


      let redisStore = new RedisStore({
        client: cacheClient,
        prefix: "sessions:",
      })

      app.use(
        session({
          store: redisStore,
          resave: false, // required: force lightweight session keep alive (touch)
          saveUninitialized: false, // recommended: only save session when data exists
          secret: sessionSecret,
          cookie: {
            secure: true,           // ✅ Use HTTPS
            httpOnly: true,         // ✅ Only accessible by the server (not client-side JS)
            sameSite: 'None'        // ✅ Allows cross-origin (e.g., Keycloak on a different domain)
          }
        })
      )
      winston.info("Express Session with Redis enabled with Secret: " + sessionSecret);


  } else {
    app.use(session({ secret: sessionSecret}));
    winston.info("Express Session enabled with Secret: " + sessionSecret);

  }

  app.use(passport.session());
  
  
}

//ATTENTION. If you use AWS Api Gateway you need also to configure the cors policy https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors-console.html
app.use(cors());
app.options('*', cors());

// const customRedisRateLimiter = require("./rateLimiter").customRedisRateLimiter;
// app.use(customRedisRateLimiter);

// MIDDLEWARE FOR REQUESTS QUOTE
// app.use('/:projectid/requests', function (req, res, next) {
  
//   console.log("MIDDLEWARE FIRED ---> REQUESTS");
//   console.log("(Requests Middleware) method: ", req.method);
//   if (req.method === 'POST') {

//   let quoteManager = new QuoteManager({ project: mockProject, tdCache: mockTdCache } )
    
//   } else {
//     next();
//   }


// });
console.log("MAX_UPLOAD_FILE_SIZE: ", process.env.MAX_UPLOAD_FILE_SIZE);


if (process.env.ROUTELOGGER_ENABLED==="true") {
  winston.info("RouterLogger enabled ");
  app.use(function (req, res, next) {
    // winston.error("log ", req);

      try {
        var projectid = req.projectid;
        winston.debug("RouterLogger projectIdSetter projectid:" + projectid);

      var fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
      winston.debug("fullUrl:"+ fullUrl);
      winston.debug(" req.get('host'):"+  req.get('host'));
     
      winston.debug("req.get('origin'):" + req.get('origin'));
      winston.debug("req.get('referer'):" + req.get('referer'));

      var routerLogger = new RouterLogger({
        origin: req.get('origin'),
        fullurl: fullUrl,    
        url: req.originalUrl.split("?").shift(),    
        id_project: projectid,      
      });

      routerLogger.save(function (err, savedRouterLogger) {        
        if (err) {
          winston.error('Error saving RouterLogger ', err)
        }
        winston.debug("RouterLogger saved "+ savedRouterLogger);
        next();
      });
      }catch(e) {
        winston.error('Error saving RouterLogger ', e)
        next();
      }
  });

} else {
  winston.info("RouterLogger disabled ");
}

app.get('/', function (req, res) {
  res.send('Hello from Tiledesk server. It\'s UP. See the documentation here http://developer.tiledesk.com');
});
  



var projectIdSetter = function (req, res, next) {
  var projectid = req.params.projectid;
  winston.debug("projectIdSetter projectid: "+ projectid);

  // if (projectid) {
    req.projectid = projectid;
  // }
  
  next()
}




var projectSetter = function (req, res, next) {
  var projectid = req.params.projectid;
  winston.debug("projectSetter projectid:" + projectid);

  if (projectid) {
    
    if (!mongoose.Types.ObjectId.isValid(projectid)) {
      //winston.warn(`Invalid ObjectId: ${projectid}`);
      return res.status(400).send({ error: "Invalid project id: " + projectid });
    }
    
    let q =  Project.findOne({_id: projectid, status: 100});
    if (cacheEnabler.project) { 
      q.cache(cacheUtil.longTTL, "projects:id:"+projectid)  //project_cache
      winston.debug('project cache enabled');
    }
    q.exec(function(err, project){
      if (err) {
        //winston.warn("Problem getting project with id: " + projectid + " req.originalUrl:  " + req.originalUrl);
      }
      winston.debug("projectSetter project:" + project);
      if (!project) {
        //winston.warn("ProjectSetter project not found with id: " + projectid);
        //next();
        return res.status(400).send({ error: "Project not found with id: " + projectid })
      } else {
        req.project = project;
        next(); //call next one time for projectSetter function
      }
    
    });
  
  }else {
    next()
  }
  

}


// app.use('/admin', admin);

//oauth2
// app.get('/dialog/authorize', oauth2.authorization);
// app.post('/dialog/authorize/decision', oauth2.decision);
// app.post('/oauth/token', oauth2.token);


// const ips = ['::1'];

app.use('/troubleshooting', troubleshooting);
app.use('/auth', auth);
app.use('/testauth', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], authtest);

app.use('/widgets', widgetsLoader);
app.use('/w', widgetsLoader);

app.use('/images', images);
app.use('/files', files);
app.use('/urls', urls);
app.use('/users', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], users);
app.use('/users_util', usersUtil);
app.use('/logs', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], logs);
app.use('/requests_util', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], requestUtilRoot);

//app.use('/webhook', webhook); // moved on top before body parser middleware

// TODO security issues
if (process.env.DISABLE_TRANSCRIPT_VIEW_PAGE ) {
  winston.info(" Transcript view page is disabled");
}else {
  app.use('/public/requests', publicRequest);
}

// project internal auth check. TODO check security issues?
app.use('/projects',project);

channelManager.use(app);

if (pubModulesManager) {
  pubModulesManager.use(app);
}

if (modulesManager) {
  modulesManager.use(app);
}

app.use('/:projectid/', [projectIdSetter, projectSetter, IPFilter.projectIpFilter, IPFilter.projectIpFilterDeny, IPFilter.decodeJwt, IPFilter.projectBanUserFilter]);


app.use('/:projectid/authtestWithRoleCheck', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], authtestWithRoleCheck);

app.use('/:projectid/project_users_test', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], project_users_test);

app.use('/:projectid/leads', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], lead);
app.use('/:projectid/requests/:request_id/messages', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes(null, ['bot','subscription'])] , message);

app.use('/:projectid/messages', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])] , messagesRootRoute);

// department internal auth check
app.use('/:projectid/departments', department);





channelManager.useUnderProjects(app);

app.use('/:projectid/groups', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], group);
app.use('/:projectid/tags', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], tag);
app.use('/:projectid/subscriptions', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], resthook);

//deprecated
app.use('/:projectid/faq', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], faq);
app.use('/:projectid/intents', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], faq);

//Deprecated??
app.use('/:projectid/faqpub', faqpub);

//deprecated
app.use('/:projectid/faq_kb', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], faq_kb);
app.use('/:projectid/bots', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], faq_kb);



// app.use('/settings',setting);

app.use('/:projectid/widgets', widgets);

// non mettere ad admin perchà la dashboard  richiama il servizio router.get('/:user_id/:project_id') spesso
// TOOD security issues. internal route check 
// app.use('/:projectid/project_users', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], project_user);
app.use('/:projectid/project_users', project_user);

// app.use('/:projectid/project_users', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], project_user);


//passport double check this and the next
app.use('/:projectid/requests', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('guest', ['bot','subscription'])], userRequest);

app.use('/:projectid/requests', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], request);


app.use('/:projectid/publicanalytics', publicAnalytics);

app.use('/:projectid/keys', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], key);

//TODO deprecated?
app.use('/:projectid/jwt', jwtroute);


app.use('/:projectid/pendinginvitations', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], pendinginvitation);
app.use('/:projectid/labels', [fetchLabels],labels);

app.use('/:projectid/campaigns',[passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], campaigns);

app.use('/:projectid/emails',[passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], email);

app.use('/:projectid/properties',[passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], property);
app.use('/:projectid/segments',[passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], segment);

app.use('/:projectid/llm', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], llm);
app.use('/:projectid/openai', openai);
app.use('/:projectid/quotes', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], quotes)

app.use('/:projectid/integration', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], integration )

app.use('/:projectid/mcp', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], mcp);

app.use('/:projectid/kbsettings', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], kbsettings);
app.use('/:projectid/kb/unanswered', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], unanswered);
app.use('/:projectid/kb/answered', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], answered);
app.use('/:projectid/kb', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], kb);

app.use('/:projectid/logs', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], logs);

app.use('/:projectid/webhooks', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], webhooks);
app.use('/:projectid/copilot', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], copilot);
app.use('/:projectid/roles', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], roles);

app.use('/:projectid/files', filesp);

if (pubModulesManager) {
  pubModulesManager.useUnderProjects(app);
}

if (modulesManager) {
  modulesManager.useUnderProjects(app);
}
 
  
// REENABLEIT
// catch 404 and forward to error handler
// app.use(function (req, res, next) {
//   var err = new Error('Not Found');
//   err.status = 404;
//   next(err);
// });

/*
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});*/





// mettere middleware qui per le quote



// error handler
app.use((err, req, res, next) => {

  winston.debug("err.name", err.name)
  if (err.name === "IpDeniedError") {
    winston.debug("IpDeniedError");
    return res.status(401).json({ err: "error ip filter" });
  }

  const realIp = req.headers['x-forwarded-for']?.split(',')[0] || req.headers['x-real-ip'] || req.ip;

  //emitted by multer when the file is too big
  if (err.code === "LIMIT_FILE_SIZE") {
    winston.debug("LIMIT_FILE_SIZE");
    winston.warn(`LIMIT_FILE_SIZE on ${req.originalUrl}`, {
      limit: process.env.MAX_UPLOAD_FILE_SIZE,
      ip: req.ip,
      realIp: realIp
    });
    return res.status(413).json({ err: "Content Too Large", limit_file_size: process.env.MAX_UPLOAD_FILE_SIZE });
  }
  
  if (err.type === "entity.too.large" || err.name === "PayloadTooLargeError") {
    winston.warn("Payload too large", { expected: err.expected, limit: err.limit, length: err.length });
    return res.status(413).json({ err: "Request entity too large", limit: err.limit});
  }


  winston.error("General error: ", err);
  return res.status(500).json({ err: "error" });
});



module.exports = app;

================================================
FILE: app.json
================================================
{
  "name": "Tiledesk Server",
  "description": "Tildesk server",
  "repository": "https://github.com/Tiledesk/tiledesk-server/new/master",
  "logo": "https://tiledesk.com/tiledesk-logo.png",
  "keywords": ["node", "express", "tiledesk", "live chat"],
  "env": {
    "FIREBASE_APIKEY": {
      "description": "Firebase API key.",
      "value": ""
    },
    "FIREBASE_AUTHDOMAIN": {
      "description": "Firebase Auth domain.",
      "value": ""
    },
    "FIREBASE_DATABASEURL": {
      "description": "Firebase database url.",
      "value": ""
    },
    "FIREBASE_STORAGEBUCKET": {
      "description": "Firebase storage bucket.",
      "value": ""
    },
    "FIREBASE_MESSAGINGSENDERID": {
      "description": "Firebase messaging sender id.",
      "value": ""
    },    
     "FIREBASE_CLIENT_EMAIL": {
      "description": "Firebase Client email.",
      "value": ""
    },
     "FIREBASE_PRIVATE_KEY": {
      "description": "Firebase private key.",
      "value": ""
    },
     "FIREBASE_PROJECT_ID": {
      "description": "Firebase project id.",
      "value": ""
    },
    "CHAT21_ENABLED": {
      "description": "Enable or disable Chat21 module.",
      "value": "true"
    },
    "CHAT21_URL": {
      "description": "The Chat21 server endpoint.",
      "value": "https://us-central1-tiledesk-test.cloudfunctions.net"
    },
    "CHAT21_APPID": {
      "description": "A unique identifier for your chat21 app.",
      "value": "myAppId"
    },
    "WIDGET_LOCATION": {
      "description": "Your widget url endpoint",
      "value": "http://localhost:4200/"
    },
    "CHAT21_ADMIN_TOKEN": {
      "description": "The Chat21 admin token.",
      "value": "youradmintoken"
    },
    "SUPER_PASSWORD": {
      "description": "Super admin password",
      "value": "superpassword"
    },
    "MONGOOSE_AUTOINDEX": {
      "description": "Autoindex",
      "value": true
    }
  },
  "image": "heroku/nodejs",
  "addons": ["mongolab"]
}


================================================
FILE: archive.sh
================================================
#!/bin/bash

# ================================
# Script interattivo per archiviare branch
# ================================
# Usage: ./archive-branch.sh [mode]
# mode: tag (default) | rename
# ================================

REMOTE="tiledesk-server"
MODE="${1:-tag}"   # default mode = tag

PROTECTED_BRANCHES=("master" "master-PRE" "master-COLLAUDO" "master-STAGE")

# Funzione per scegliere un branch valido
get_branch_to_archive() {
  local branch="$1"

  while true; do
    # Se branch protetto, avvisa
    if [[ " ${PROTECTED_BRANCHES[@]} " =~ " ${branch} " ]]; then
      echo "❌ Il branch '$branch' è protetto e non può essere archiviato."
    fi

    # Controlla che il branch locale esista
    if git show-ref --verify --quiet refs/heads/"$branch"; then
      break  # branch valido trovato
    fi

    # Richiedi input all’utente
    read -p "Inserisci un branch locale valido da archiviare (oppure 'quit' per annullare): " branch
    if [[ "$branch" == "quit" || "$branch" == "q" ]]; then
      echo "Operazione annullata."
      exit 0
    fi
  done

  echo "$branch"
}

# Branch corrente
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)

# Se branch corrente non è protetto, chiedi conferma
if [[ ! " ${PROTECTED_BRANCHES[@]} " =~ " ${CURRENT_BRANCH} " ]]; then
  echo "⚡ Branch corrente: $CURRENT_BRANCH"
  read -p "Vuoi archiviare questo branch? (y/n): " CONFIRM
  if [[ "$CONFIRM" != "y" ]]; then
    read -p "Inserisci il branch da archiviare (oppure 'quit' per annullare): " USER_BRANCH
    if [[ "$USER_BRANCH" == "quit" || "$USER_BRANCH" == "q" ]]; then
      echo "Operazione annullata."
      exit 0
    fi
    CURRENT_BRANCH=$(get_branch_to_archive "$USER_BRANCH")
  fi
else
  # Branch corrente è protetto, chiedi un branch alternativo
  CURRENT_BRANCH=$(get_branch_to_archive "$CURRENT_BRANCH")
fi

# Operazione di archiviazione
if [ "$MODE" == "tag" ]; then
  ARCHIVE_TAG="archive/$(echo $CURRENT_BRANCH | tr '/' '-')"
  echo "Creando tag di archivio: $ARCHIVE_TAG"
  git tag "$ARCHIVE_TAG" "$CURRENT_BRANCH"
  git push "$REMOTE" "$ARCHIVE_TAG"

  echo "Eliminando branch remoto $CURRENT_BRANCH"
  git push "$REMOTE" --delete "$CURRENT_BRANCH"

  echo "Eliminando branch locale $CURRENT_BRANCH"
  git branch -d "$CURRENT_BRANCH"

  echo "✅ Branch '$CURRENT_BRANCH' archiviato con tag '$ARCHIVE_TAG'"

elif [ "$MODE" == "rename" ]; then
  ARCHIVE_BRANCH="archive/$CURRENT_BRANCH"
  echo "Rinomino branch locale in: $ARCHIVE_BRANCH"
  git branch -m "$CURRENT_BRANCH" "$ARCHIVE_BRANCH"

  echo "Pusho nuovo branch su remote $REMOTE"
  git push "$REMOTE" "$ARCHIVE_BRANCH"

  echo "Eliminando branch remoto vecchio $CURRENT_BRANCH"
  git push "$REMOTE" --delete "$CURRENT_BRANCH"

  echo "✅ Branch '$CURRENT_BRANCH' archiviato come '$ARCHIVE_BRANCH'"

else
  echo "❌ Modalità non valida. Usa 'tag' o 'rename'."
  exit 1
fi

================================================
FILE: bin/www
================================================
#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('tiledesk-server:server');
var http = require('http');
var version = require('../package.json').version
var mongoose = require('mongoose');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */


var httpServer = http.createServer(app);

var webSocketServer = require('../websocket/webSocketServer');
webSocketServer.init(httpServer);


/**
 * Listen on provided port, on all network interfaces.
 */

// use ipv4 or ipv6 https://stackoverflow.com/questions/50855419/get-only-ipv4-ips-via-nodejs-express
// var listener = httpServer.listen(port,'0.0.0.0', function(){      

var listener = httpServer.listen(port, function(){      
    console.log('Listening tiledesk-server ver:'+version+' on port ' + listener.address().port); //Listening on port 8888
});

httpServer.on('error', onError);
httpServer.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = httpServer.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}


================================================
FILE: channels/channelManager.js
================================================

var winston = require('../config/winston');


var chat21Enabled = process.env.CHAT21_ENABLED;
winston.debug("chat21Enabled: "+chat21Enabled);

var engine = process.env.CHAT21_ENGINE;
winston.debug("chat21 engine: "+engine);


var validtoken = require('../middleware/valid-token');
var passport = require('passport');
require('../middleware/passport')(passport);


if (chat21Enabled && chat21Enabled == "true") {
    winston.info("ChannelManager - Chat21 channel is enabled");
}else {
    winston.warn("ChannelManager Chat21 channel is disabled. Attention!!");
}

class ChannelManager {

    use(app) {
        var that = this;
        winston.debug("ChannelManager using controllers");

        if (chat21Enabled && chat21Enabled == "true") {

            var chat21WebHook = require('../channels/chat21/chat21WebHook');
            app.use('/chat21/requests',  chat21WebHook); //<- TODO cambiare /request in /webhook

            var chat21Contact = require('../channels/chat21/chat21Contact');
            app.use('/chat21/contacts',  chat21Contact);

            var chat21ConfigRoute = require('../channels/chat21/configRoute');
            app.use('/chat21/config',  chat21ConfigRoute);

            
            if (engine && engine=="firebase") {
                winston.info("ChannelManager - Chat21 channel engine is firebase");
                var firebaseAuth = require('../channels/chat21/firebaseauth');
                app.use('/chat21/firebase/auth', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], firebaseAuth);
            } else { //if (engine && engine=="native") {
                winston.info("ChannelManager - Chat21 channel engine is native mqtt");
                var nativeAuth = require('../channels/chat21/nativeauth');
                app.use('/chat21/native/auth', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], nativeAuth);
            }
            winston.info("ChannelManager - Chat21 channel routes initialized");
        } else {
            winston.info("ChannelManager - Chat21 channel routes not initialized.");
        }            

        
    }

    useUnderProjects(app) {

    }

    listen() {
        var that = this;

        if (process.env.NODE_ENV == 'test')  {	
            return winston.info("ChannelManager listener disabled for testing");
        }
        
        if (chat21Enabled && chat21Enabled == "true") {   
            var chat21Handler = require('../channels/chat21/chat21Handler');         
            chat21Handler.listen();
            winston.info("ChannelManager listener started");
        }else {
            winston.info("ChannelManager listener NOT started ");
        }
    }


    
}

var channelManager = new ChannelManager();
module.exports = channelManager;


================================================
FILE: channels/chat21/chat21Client.js
================================================

var chat21Config = require('../../channels/chat21/chat21Config');
var Chat21 = require('@chat21/chat21-node-sdk');
var winston = require('../../config/winston');



var url = process.env.CHAT21_URL || chat21Config.url;
var appid = process.env.CHAT21_APPID || chat21Config.appid;

winston.info('Chat21Client chat21.url: '+url );
winston.info('Chat21Client chat21.appid: '+ appid);

var chat21 = new Chat21({
 url: url,
 appid: appid
 //authurl: process.env.CHAT21_AUTH_URL
});

module.exports = chat21;


================================================
FILE: channels/chat21/chat21Config.js
================================================
module.exports = {
  'url': 'https://CHANGEIT.cloudfunctions.net',
  'appid': 'tilechat',
  'adminToken' : 'chat21-secret-orgAa,'
};


================================================
FILE: channels/chat21/chat21Contact.js
================================================
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');

var Project_user = require("../../models/project_user");

var winston = require('../../config/winston');

// THE THREE FOLLOWS IMPORTS  ARE USED FOR AUTHENTICATION IN THE ROUTE
var passport = require('passport');
require('../../middleware/passport')(passport);
var validtoken = require('../../middleware/valid-token')
var RoleConstants = require("../../models/roleConstants");
var cacheUtil = require('../../utils/cacheUtil');
const { Query } = require('mongoose');


router.get('/:contact_id', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], async (req, res) => {
  winston.debug('REQ USER ID ', req.user._id);
  
  var isObjectId = mongoose.Types.ObjectId.isValid(req.user._id);
  winston.debug("isObjectId:"+ isObjectId);     

  var query = { role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}, status: "active" };
  winston.debug(' query: ',query);


  if (isObjectId) {
    query.id_user = req.user._id;
    // query.id_user = mongoose.Types.ObjectId(contact_id);
  } else {
    query.uuid_user = req.user._id;
  }

  var projects = await Project_user.find(query).    
    exec(); 

  var projectsArray = [];
  projects.forEach(project => {
    projectsArray.push(project.id_project);
    // projectsArray.push(mongoose.Types.ObjectId(project.id_project));
  });
    

  var contact_id = req.params.contact_id;
  winston.debug('contact_id: '+ contact_id);

  isObjectId = mongoose.Types.ObjectId.isValid(contact_id);
  winston.debug("isObjectId:"+ isObjectId);                             

  query = { id_project: { $in : projectsArray }, role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}, status: "active" };
  winston.debug(' query: ',query);

  if (isObjectId) {
    query.id_user = contact_id;
    // query.id_user = mongoose.Types.ObjectId(contact_id);
  } else {
    query.uuid_user = contact_id;
  }

  winston.debug("query: ", query);

  var teammates = await Project_user.find(query).
  populate('id_project').
  populate('id_user').
  exec(); 

  var result = [];
  if (teammates && teammates.length>0) {  
    teammates.forEach(teammate => {
      
      var contact = {};
      if (teammate.id_user) {
        contact.uid = teammate.id_user._id;
        contact.email = teammate.id_user.email;
        contact.firstname = teammate.id_user.firstname;
        contact.lastname = teammate.id_user.lastname;

        if (teammate.id_project) {
          contact.description =  teammate.id_project.name;
        }

        // if (teammate.id_user.createdAt) {
        //   contact.timestamp = teammate.id_user.createdAt.getTime();
        // }
        
        // winston.info("teammate: "+ JSON.stringify(teammate));

        var contactFound = result.filter(c => c.uid === contact.uid );
        winston.debug("contactFound: "+ JSON.stringify(contactFound));

        // var index = result.indexOf(contactFound);
        let index = result.findIndex(c => c.uid === contact.uid );

        winston.debug("index: "+ index);

        if (contactFound.length==0) {
          winston.debug("not found");
          result.push(contact);
        }else {
          winston.debug("found",contactFound);
          // contactFound[0].description = "sssss";
          contactFound[0].description= contactFound[0].description + ", "+teammate.id_project.name;
          result[index] = contactFound[0];
          
        }
      }
      

    });
  }

  winston.debug("send");

  if (result && result.length>0) {
    res.json(result[0]);
  }else {
    res.json({});
  }
  
    
  
});




router.get('/', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], async (req, res) => {
  winston.debug('REQ USER ID ', req.user._id);

  var direction = -1; //-1 descending , 1 ascending
  if (req.query.direction) {
    direction = req.query.direction;
  } 
  winston.debug("direction",direction);

  var sortField = "updatedAt";
  if (req.query.sort) {
    sortField = req.query.sort;
  } 
  winston.debug("sortField",sortField);

  var sortQuery={};
  sortQuery[sortField] = direction;

  var query = { role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}, status: "active" };

  var isObjectId = mongoose.Types.ObjectId.isValid(req.user._id);
  winston.debug("isObjectId:"+ isObjectId);                             

  if (isObjectId) {
    query.id_user = req.user._id;
    // query.id_user = mongoose.Types.ObjectId(contact_id);
  } else {
    query.uuid_user = req.user._id;
  }



  var projects = await Project_user.find(query).    
    exec(); 

    var projectsArray = [];
    projects.forEach(project => {
      projectsArray.push(project.id_project);
    });
    
    var query = { id_project: { $in : projectsArray }, role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}, status: "active" };
    winston.debug("query: ", query);

    var teammates = await Project_user.find(query).
    populate('id_project').
    populate('id_user').
    sort(sortQuery).
    exec(); 

    var result = [];

    if (teammates && teammates.length>0) {  
      teammates.forEach(teammate => {
        
        var contact = {};
        if (teammate.id_user) {
          contact.uid = teammate.id_user._id;
          contact.email = teammate.id_user.email;
          contact.firstname = teammate.id_user.firstname;
          contact.lastname = teammate.id_user.lastname;

          if (teammate.id_project) {
            contact.description =  teammate.id_project.name;
          }

          // if (teammate.id_user.createdAt) {
          //   contact.timestamp = teammate.id_user.createdAt.getTime();
          // }
          
          // winston.info("teammate: "+ JSON.stringify(teammate));

          var contactFound = result.filter(c => c.uid === contact.uid );
          winston.debug("contactFound: "+ JSON.stringify(contactFound));

          // var index = result.indexOf(contactFound);
          let index = result.findIndex(c => c.uid === contact.uid );

          winston.debug("index: "+ index);

          if (contactFound.length==0) {
            winston.debug("not found");
            result.push(contact);
          }else {
            winston.debug("found",contactFound);
            // contactFound[0].description = "sssss";
            contactFound[0].description= contactFound[0].description + ", "+teammate.id_project.name;
            result[index] = contactFound[0];
            
          }
        }
        

      });
    }
    winston.debug("send");
    res.json(result);
    
  
});





module.exports = router;


================================================
FILE: channels/chat21/chat21Event.js
================================================
const EventEmitter = require('events');

class Chat21Event extends EventEmitter {}

const chat21Event = new Chat21Event();




module.exports = chat21Event;


================================================
FILE: channels/chat21/chat21Handler.js
================================================

const messageEvent = require('../../event/messageEvent');
const authEvent = require('../../event/authEvent');
const botEvent = require('../../event/botEvent');
const requestEvent = require('../../event/requestEvent');
const groupEvent = require('../../event/groupEvent');
const chat21Event = require('./chat21Event');
const leadEvent = require('../../event/leadEvent');

var messageService = require('../../services/messageService');
var MessageConstants = require("../../models/messageConstants");
var ChannelConstants = require("../../models/channelConstants");
var winston = require('../../config/winston');
var Request = require("../../models/request");
var chat21Config = require('./chat21Config');
var chat21 = require('./chat21Client');





const MaskData = require("maskdata");

const maskPasswordOptions = {
    // Character to mask the data. default value is '*'
    maskWith : "*",
    //Should be positive Integer
    // If the starting 'n' digits needs to be unmasked
    // Default value is 4
    unmaskedStartDigits: 3, 
    
    // Should be positive Integer
    //If the ending 'n' digits needs to be unmasked
    // Default value is 1
    unmaskedEndDigits: 2
  };




// var chat21Util = require('./chat21Util');
// var tiledeskUtil = require('./tiledesk-util');

var adminToken =  process.env.CHAT21_ADMIN_TOKEN || chat21Config.adminToken;

const masked_adminToken = MaskData.maskPhone(adminToken, maskPasswordOptions);

winston.info('Chat21Handler adminToken: '+ masked_adminToken);




class Chat21Handler {

 
    typing(message, timestamp) {
        return new Promise(function (resolve, reject) {

            // if privateFor skip typing
            //no typing for subtype info
            if (message.attributes && message.attributes.subtype && (message.attributes.subtype==='info' || message.attributes.subtype==='private')) {
                return resolve();
            }else {
                chat21.conversations.typing(message.recipient, message.sender, message.text, timestamp).finally(function() {
                    return resolve();
                });
            }
            

        });
    }


    listen() {

        var that = this;       
       
        winston.debug("Chat21Handler listener start ");
        

        if (process.env.SYNC_CHAT21_GROUPS !=="true") {
            winston.info("Sync Tiledesk to Chat21 groups disabled");
            // return; questo distrugge il tread. attento non lo mettere +
        }

        
      
        // chat21Handler on worker is loaded with stadard events like request.create and NOT request.create.queue because it is used internally by the worker when the request is closed by ChatUnhandledRequestScheduler

        // su projectUser create e update
        authEvent.on('user.signup', function(userData) {
            var firstName = userData.savedUser.firstname;
            var lastName = userData.savedUser.lastname;
            var email = userData.savedUser.email;
            var current_user = userData.savedUser.id;

            setImmediate(() => {
                winston.debug("Chat21Handler on user.signup ",  userData);

                chat21.auth.setAdminToken(adminToken);

                // create: function(firstname, lastname, email, current_user){
                chat21.contacts.create(firstName, lastName, email, current_user).then(function(data) {
                    winston.verbose("Chat21 contact created: " + JSON.stringify(data));      
                    chat21Event.emit('contact.create', data);                                          
                }).catch(function(err) {
                    winston.error("Error creating chat21 contact ", err);
                    chat21Event.emit('contact.create.error', err);
                });

            });
        });


        authEvent.on('user.update', function(userData) {
            var firstName = userData.updatedUser.firstname;
            var lastName = userData.updatedUser.lastname;            
            var current_user = userData.updatedUser.id;

            setImmediate(() => {
                winston.debug("Chat21Handler on user.update ",  userData);

                chat21.auth.setAdminToken(adminToken);

                // update: function(firstname, lastname, current_user){
                chat21.contacts.update(firstName, lastName, current_user).then(function(data) {
                    winston.verbose("Chat21 contact updated: " + JSON.stringify(data));      
                    chat21Event.emit('contact.update', data);                                          
                }).catch(function(err) {
                    winston.error("Error updating chat21 contact ", err);
                    chat21Event.emit('contact.update.error', err);
                });

            });
        });


        botEvent.on('faqbot.create', function(bot) {
            var firstName = bot.name;
            var lastName = "";
            var email = "";
            // botprefix
            var current_user = "bot_"+bot.id;

            setImmediate(() => {
                winston.debug("Chat21Handler on faqbot.create ",  bot);

                chat21.auth.setAdminToken(adminToken);

                // create: function(firstname, lastname, email, current_user){
                chat21.contacts.create(firstName, lastName, email, current_user).then(function(data) {                    
                    winston.verbose("Chat21 contact created: " + JSON.stringify(data));         
                    chat21Event.emit('contact.create', data);                                          
                }).catch(function(err) {
                    winston.error("Error creating chat21 contact ", err);
                    chat21Event.emit('contact.create.error', err);
                });

            });
        });



        botEvent.on('faqbot.update', function(bot) {
            var firstName = bot.name;
            var lastName = "";
            // botprefix
            var current_user = "bot_"+bot.id;

            setImmediate(() => {
                winston.debug("Chat21Handler on faqbot.create ",  bot);

                chat21.auth.setAdminToken(adminToken);

               // update: function(firstname, lastname, current_user){
                chat21.contacts.update(firstName, lastName, current_user).then(function(data) {
                    winston.verbose("Chat21 contact updated: " + JSON.stringify(data));      
                    chat21Event.emit('contact.update', data);                                          
                }).catch(function(err) {
                    winston.error("Error updating chat21 contact ", err);
                    chat21Event.emit('contact.update.error', err);
                });

            });
        });


    // quando passa da lead temp a default aggiorna tutti va bene?        
         leadEvent.on('lead.update', function(lead) {
            //  non sembra funzionare chiedi a Dario dove prende le info
            setImmediate(() => {
                winston.debug("Chat21Handler on lead.update ",  lead);

                //  TODO AGGIORNA SOLO SE PASSA DA GUEST A ALTRO??
                Request.find({lead: lead._id, id_project: lead.id_project}, function(err, requests) {

                    if (err) {
                        winston.error("Error getting request by lead", err);
                        return 0;
                    }
                    if (!requests || (requests && requests.length==0)) {
                        winston.verbose("No request found for lead id " +lead._id );
                        return 0;
                    }
                    
                    chat21.auth.setAdminToken(adminToken);

                    requests.forEach(function(request) {
                        if (request.channelOutbound.name === ChannelConstants.CHAT21) {

                            winston.verbose("Chat21Handler lead.update for request ",  request);
                            
                            var groupName = lead.fullname;
                            if (request.subject) {
                                groupName=request.subject;
                            }
                            // update: function(name, owner, attributes, group_id){

                            chat21.groups.update(groupName, undefined, undefined, request.request_id).then(function(data) {
                                winston.verbose("Chat21 group updated for lead.update: " + JSON.stringify(data));      
                                chat21Event.emit('group.update', data);                                          
                            }).catch(function(err) {
                                winston.error("Error updating chat21 group for lead.update", err);
                                chat21Event.emit('group.update.error', err);
                            });

                             // updateAttributes: function(attributes, group_id){
                                 var gattributes = {userFullname:lead.fullname, userEmail: lead.email }
                                //  qui1
                            chat21.groups.updateAttributes(gattributes, request.request_id).then(function(data) {
                                winston.verbose("Chat21 group gattributes for lead.update updated: " + JSON.stringify(data));      
                                chat21Event.emit('group.update', data);        
                                chat21Event.emit('group.attributes.update', data);                                          
                            }).catch(function(err) {
                                winston.error("Error updating chat21 gattributes for lead.update group ", err);
                                chat21Event.emit('group.attributes.update.error', err);
                            });


                        }
                    })
                  
                });

              
            });
        });


        messageEvent.on('message.test', function(message) {
        
            winston.info("Chat21Sender message.test");

            chat21.auth.setAdminToken(adminToken);

            return  chat21.messages.sendToGroup(message.senderFullname,     message.recipient, 
                message.recipient_fullname, message.text, message.sender, message.attributes, message.type, message.metadata, message.timestamp, message.group)
                        .then(function(data){
                            winston.info("Chat21Sender sendToGroup test: "+ JSON.stringify(data));
                        });
        });
       
        // chat21Handler on worker is loaded with stadard events like request.create and NOT request.create.queue because it is used internally by the worker when the request is closed by ChatUnhandledRequestScheduler
        messageEvent.on('message.sending', function(message) {

            // setImmediate(() => {
                // TODO perche nn c'è setImmedite? per performace


                    winston.verbose("Chat21Sender on message.sending: "+  message.text);
                    winston.debug("Chat21Sender on message.sending ",  message);
                   
                   if (message && 
                    message.status === MessageConstants.CHAT_MESSAGE_STATUS.SENDING &&
                    message.channel_type ==  MessageConstants.CHANNEL_TYPE.GROUP &&
                    message.request && 
                    message.request.channelOutbound.name == ChannelConstants.CHAT21) { //here only request.channelOutbound is important because chat21handler is for outgoing messages( from Tiledesk to agents clients)

                    
                        chat21.auth.setAdminToken(adminToken);


                        //chat21.conversations.typing(message.recipient, message.sender, message.text, new Date()).finally(function() {
                        return that.typing(message,new Date() ).then(function() {
                       

                        let attributes = message.attributes;

                        if (!attributes) attributes = {};
                        
                        attributes['tiledesk_message_id'] = message._id;

                        attributes['projectId'] = message.id_project; //TODO not used. used by ionic to open request detail ???
                        
                        if (message.channel && message.channel.name) {
                            attributes['channel'] = message.channel.name;
                        }
                        
                       


                        winston.verbose("Chat21Sender sending message.sending: "+  message.text);
                        winston.debug("Chat21Sender sending message.sending ",  message);

                        // chat21Util.getParsedMessage().then(function(messageData) {
                        //     message = messageData;

                            // doent'work must merge older field with new message = chat21Util.parseReply(message.text);

                            // sendToGroup: function(sender_fullname, recipient_id, recipient_fullname, text, sender_id, attributes, type, metadata, timestamp){


                            var timestamp = Date.now();
                            // var timestamp = undefined;
                            if (message.attributes && message.attributes.clienttimestamp) {
                                timestamp = message.attributes.clienttimestamp;
                            }

                            var recipient_fullname = "Guest"; 
                            // guest_here
                        
                            if (message.request && message.request.lead && message.request.lead.fullname) {
                                recipient_fullname = message.request.lead.fullname;
                            }
                            if (message.request && message.request.subject ) {
                                recipient_fullname = message.request.subject;
                            }
                            if (message.request && message.request.channel  && message.request.channel.name ) {
                                attributes['request_channel'] = message.request.channel.name;
                            }

                            /*
                            const parsedReply = tiledeskUtil.parseReply(message.text);
                            winston.info("Chat21 sendToGroup parsedMessage " + JSON.stringify(parsedReply));

                            // message = {...message, ...parsedReply.message };
                            // merge(message, parsedReply.message );

                            if (parsedReply.message.text) {
                                message.text = parsedReply.message.text;
                            }
                            if (parsedReply.message.type) {
                                message.type = parsedReply.message.type;
                            }
                            if (parsedReply.message.type) {
                                message.metadata = parsedReply.message.metadata;
                            }
                            
                            // var msg_attributes = {...message.attributes, ...parsedReply.message.attributes };
                            if (parsedReply.message && parsedReply.message.attributes) {
                                for(const [key, value] of Object.entries(parsedReply.message.attributes)) {
                                    attributes[key] = value
                                }
                            }    
                            */   
                         
                            // performance console log
                            // console.log("************* send message chat21: "+new Date().toISOString(), message.text );

                           return  chat21.messages.sendToGroup(message.senderFullname,     message.recipient, 
                                recipient_fullname, message.text, message.sender, attributes, message.type, message.metadata, timestamp)
                                        .then(function(data){
                                            winston.verbose("Chat21Sender sendToGroup sent: "+ JSON.stringify(data) + " for text message " + message.text);
                                    

                                            // chat21.conversations.stopTyping(message.recipient,message.sender);
                                            
                                            
                                            // performance console log
                                            // console.log("************* senttt message chat21: "+new Date().toISOString(), message.text );

                                            chat21Event.emit('message.sent', data);
    
                                                winston.debug("Message changeStatus 1");
                                                messageService.changeStatus(message._id, MessageConstants.CHAT_MESSAGE_STATUS.DELIVERED) .then(function(upMessage){
                                                    winston.debug("Chat21 message sent ", upMessage.toObject());                                        
                                                }).catch(function(err) {
                                                    winston.error("Error Chat21 message sent with id: "+message._id, err);                                        
                                                });
    
                                }).catch(function(err) {
                                    winston.error("Chat21 sendToGroup err", err);
                                    chat21Event.emit('message.sent.error', err);
                                });

                            });
                        
                        // });
                    }
                    else if (message &&
                         message.status === MessageConstants.CHAT_MESSAGE_STATUS.SENDING && 
                         message.channel_type ==  MessageConstants.CHANNEL_TYPE.DIRECT &&
                         message.channel.name == ChannelConstants.CHAT21) {
                        
                            // winston.warn("Chat21Sender this is a direct message. Unimplemented method", message);

                            chat21.auth.setAdminToken(adminToken);

                            winston.debug("Chat21Sender");
                            // send: function(sender_fullname, recipient_id, recipient_fullname, text, sender_id, attributes, type, metadata){
                           return  chat21.messages.send(message.senderFullname,     message.recipient, 
                            message.recipientFullname, message.text, message.sender, message.attributes, message.type, message.metadata)
                                    .then(function(data){
                                        winston.verbose("Chat21Sender send sent: "+ JSON.stringify(data));
                                

                                        // chat21.conversations.stopTyping(message.recipient,message.sender);

                                        chat21Event.emit('message.sent', data);
                                            winston.debug("Message changeStatus 2");
                                            messageService.changeStatus(message._id, MessageConstants.CHAT_MESSAGE_STATUS.DELIVERED) .then(function(upMessage){
                                                winston.debug("Chat21 message sent ", upMessage.toObject());                                        
                                            }).catch(function(err) {
                                                winston.error("Error Chat21 message sent with id: "+message._id, err);                                        
                                            });

                            }).catch(function(err) {
                                winston.error("Chat21 send direct err", err);
                                chat21Event.emit('message.sent.error', err);
                            });

                            

                    } 
                    
                    else if (message &&
                        message.status === MessageConstants.CHAT_MESSAGE_STATUS.SENDING && 
                        message.channel_type ==  MessageConstants.CHANNEL_TYPE.GROUP &&
                        message.channel.name == ChannelConstants.CHAT21) {
                       
                           // winston.warn("Chat21Sender this is a group message. Unimplemented method", message);

                           chat21.auth.setAdminToken(adminToken);

                           var timestamp = Date.now();
                           // var timestamp = undefined;
                           if (message.attributes && message.attributes.clienttimestamp) {
                               timestamp = message.attributes.clienttimestamp;
                           }


                           return  chat21.messages.sendToGroup(message.senderFullname,     message.recipient, 
                            message.recipientFullname, message.text, message.sender, message.attributes, message.type, message.metadata, timestamp)                         
                                   .then(function(data){
                                       winston.verbose("Chat21Sender send sent: "+ JSON.stringify(data));
                               

                                       // chat21.conversations.stopTyping(message.recipient,message.sender);

                                       chat21Event.emit('message.sent', data);
                                       winston.debug("Message changeStatus 3");
                                           messageService.changeStatus(message._id, MessageConstants.CHAT_MESSAGE_STATUS.DELIVERED) .then(function(upMessage){
                                               winston.debug("Chat21 message sent ", upMessage.toObject());                                        
                                           }).catch(function(err) {
                                               winston.error("Error Chat21 message sent with id: "+message._id, err);                                        
                                           });

                           }).catch(function(err) {
                               winston.error("Chat21 sendToGroup err", err);
                               chat21Event.emit('message.sent.error', err);
                           });

                           

                   } else {
                        winston.error("Chat21Sender this is not a group o direct message", message);
                        return;
                    }
                // });
            });


            requestEvent.on('request.attributes.update',  function(request) {          

                setImmediate(() => {
                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {

                        chat21.auth.setAdminToken(adminToken);

                        var gattributes = request.attributes;
                        // qui1
                        chat21.groups.updateAttributes(gattributes, request.request_id).then(function(data) {
                            winston.verbose("Chat21 group gattributes for request.attributes.update updated: " + JSON.stringify(data));      
                            chat21Event.emit('group.update', data);        
                            chat21Event.emit('group.attributes.update', data);                                          
                        }).catch(function(err) {
                            winston.error("Error updating chat21 gattributes for request.attributes.update group ", err);
                            chat21Event.emit('group.attributes.update.error', err);
                        });

                    }
                });
            });

            
            //   not used now. Before used by ionic
            // requestEvent.on('request.update',  function(request) {          

            //     setImmediate(() => {
            //         if (request.channelOutbound.name === ChannelConstants.CHAT21) {

            //             chat21.auth.setAdminToken(adminToken);

            //               // https://stackoverflow.com/questions/42310950/handling-undefined-values-with-firebase/42315610                        
            //             //   var requestWithoutUndefined = JSON.parse(JSON.stringify(request, function(k, v) {
            //             //     if (v === undefined) { return null; } return v; 
            //             //  }));

            //             // var gattributes = { "_request":requestWithoutUndefined};

            //             // qui1
            //             chat21.groups.updateAttributes(gattributes, request.request_id).then(function(data) {
            //                 winston.verbose("Chat21 group gattributes for request.update updated: " +  JSON.stringify(data));      
            //                 chat21Event.emit('group.update', data);        
            //                 chat21Event.emit('group.attributes.update', data);                                          
            //             }).catch(function(err) {
            //                 winston.error("Error updating chat21 gattributes for request.update group ", err);
            //                 chat21Event.emit('group.attributes.update.error', err);
            //             });

            //         }
            //     });
            // });


            // chat21Handler on worker is loaded with stadard events like request.create and NOT request.create.queue because it is used internally by the worker when the request is closed by ChatUnhandledRequestScheduler
            requestEvent.on('request.create',  function(request) {          

                winston.debug("chat21Handler requestEvent request.create called" , request);
                // setImmediate(() => {
// perche nn c'è setImmedite? per performace
                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {

                        chat21.auth.setAdminToken(adminToken);

                        

                        // let requestObj = request.toObject();
                        let requestObj = request.toJSON();
                        
                        winston.verbose("creating chat21 group for request with id: " + requestObj._id);

                        // winston.info("requestObj.participants: "+ Object.prototype.toString.call(requestObj.participants));
                        winston.debug("requestObj.participants: "+ JSON.stringify(requestObj.participants));
                        
                        let members = requestObj.participants;
                        // var members = reqParticipantArray;

                        members.push("system");
                        if (request.lead) {
                            // lead_id used. Change it?
                            members.push(request.lead.lead_id);
                        }
                        
                        
                        // let membersArray = JSON.parse(JSON.stringify(members));
                        // winston.info("membersArray", membersArray);

                        var gAttributes = requestObj.attributes || {};
                        // TODO ATTENTION change value by reference
                        // var gAttributes = request.attributes || {}; //BUG LINK TO event emmiter obiect


                        // problema requester_id
                        gAttributes["requester_id"] = request.requester_id;
                    
                       
                        gAttributes['projectId'] = request.id_project; //used by ionic to open request detail 

                        if (request.lead) {
                            gAttributes['userFullname'] = request.lead.fullname; //used by ionic to open request detail 
                            gAttributes['userEmail'] = request.lead.email; //used by ionic to open request detail 
                            // TOOD is it necessary? 
                            // lead_id used. Change it?
                            gAttributes['senderAuthInfo'] = {authType: "USER", authVar: {uid:request.lead.lead_id}}; //used by ionic otherwise ionic dont show userFullname in the participants panel
                        }
                        // TODO ionic dont show attributes panel if attributes.client is empty. bug?
                        gAttributes['client'] = request.userAgent || 'n.d.'; //used by ionic to open request detail 
                        if (request.department) {
                            gAttributes['departmentId'] = request.department._id; //used by ionic to open request detail 
                            gAttributes['departmentName'] = request.department.name; //used by ionic to open request detail 
                        }                        
                        gAttributes['sourcePage'] = request.sourcePage; //used by ionic to open request detail 

                        
                        // https://stackoverflow.com/questions/42310950/handling-undefined-values-with-firebase/42315610

                        //   not used now. Before used by ionic
                        // var requestWithoutUndefined = JSON.parse(JSON.stringify(request, function(k, v) {
                        //     if (v === undefined) { return null; } return v; 
                        //  }));
                        //  gAttributes['_request'] = requestWithoutUndefined; //used by ionic to open request detail 
                        
                        


 
                        winston.debug("Chat21 group create gAttributes: ",gAttributes);  

                        var groupId = request.request_id;

                        var group_name = "Guest"; 
                        // guest_here
                        
                        if (request.lead && request.lead.fullname) {
                            group_name = request.lead.fullname;
                        }
                        if (request.subject) {
                            group_name = request.subject;
                        }
                        
                        // performance console log
                        // console.log("************* before request.support_group.created: "+new Date().toISOString());

                        //TODO racecondition?
                        return chat21.groups.create(group_name, members, gAttributes, groupId).then(function(data) {
                                winston.verbose("Chat21 group created: " + JSON.stringify(data));     

                                // performance console log
                                // console.log("************* after request.support_group.created: "+new Date().toISOString()); 

                                requestEvent.emit('request.support_group.created', request);

                                chat21Event.emit('group.create', data);      
                            }).catch(function(err) {
                                winston.error("Error creating chat21 group ", err);

                                requestEvent.emit('request.support_group.created.error', request);

                                chat21Event.emit('group.create.error', err);
                            });


                    }
                // });
            });
    

            // chat21Handler on worker is loaded with stadard events like request.create and NOT request.create.queue because it is used internally by the worker when the request is closed by ChatUnhandledRequestScheduler

            requestEvent.on('request.close',  function(request) {          //request.close event here noqueued
                winston.debug("request.close event here 8")
                winston.debug("chat21Handler requestEvent request.close called" , request);

                setImmediate(() => {
                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {

                        chat21.auth.setAdminToken(adminToken);                      

                        winston.debug("Chat21Sender archiving conversations for ",request.participants);

                       //iterate request.participant and archive conversation
                       request.participants.forEach(function(participant,index) {

                        winston.debug("Chat21Sender archiving conversation: " + request.request_id + "for " + participant);

                            chat21.conversations.archive(request.request_id, participant)
                                        .then(function(data){
                                            winston.verbose("Chat21 conversation archived result "+ JSON.stringify(data));
                                    
                                            chat21Event.emit('conversation.archived', data);                                               

                                }).catch(function(err) {
                                    winston.error("Chat21 archived err", err);
                                    chat21Event.emit('conversation.archived.error', err);
                                });
                       });

                        //    archive: function(recipient_id, user_id){
                       chat21.conversations.archive(request.request_id, "system")
                       .then(function(data){
                           winston.verbose("Chat21 archived for system"+ JSON.stringify(data));
                   
                           chat21Event.emit('conversation.archived', data);                                               

                        }).catch(function(err) {
                            winston.error("Chat21 archived  for system err", err);
                            chat21Event.emit('conversation.archived.error', err);
                        });

                        
                        //  request.lead can be undefined because some test case uses the old deprecated method requestService.createWithId.
                        if (request.lead) {
                            // lead_id used. Change it?
                            chat21.conversations.archive(request.request_id, request.lead.lead_id)  //                        chat21.conversations.archive(request.request_id, request.requester_id)<-desnt'archive

                            .then(function(data){
                                winston.verbose("Chat21 archived for request.lead.lead_id"+ JSON.stringify(data));
                        
                                chat21Event.emit('conversation.archived', data);                                               
     
                             }).catch(function(err) {
                                 winston.error("Chat21 archived for request.lead.lead_id err", err);
                                 chat21Event.emit('conversation.archived.error', err);
                             });
                        }
                        
                    }
                });
            });
            
            requestEvent.on('request.delete', function(request) {

                winston.debug("chat21Handler requestEvent request.delete called" , request);

                setImmediate(() => {
                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {

                        chat21.auth.setAdminToken(adminToken);

                        winston.debug("Chat21Sender deleting conversations for ",request.participants);

                        chat21.conversations.delete(request.request_id).then((data) => {
                            winston.verbose("Chat21 conversation archived result "+ JSON.stringify(data));
                            chat21Event.emit('conversation.deleted', data);                                               
                        }).catch((err) => {
                            winston.error("Chat21 deleted err", err);
                            chat21Event.emit('conversation.deleted.error', err);
                        })
                    }
                })

            });

             requestEvent.on('request.participants.update',  function(data) {      
                 
                winston.debug("chat21Handler requestEvent request.participants.update called" , data);

                   let request = data.request;
                   let removedParticipants = data.removedParticipants;
                

                setImmediate(() => {
                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {

                        chat21.auth.setAdminToken(adminToken);

                        

                     
                        let requestObj = request.toJSON();
                        
                        winston.verbose("setting chat21 group for request.participants.update for request with id: " + requestObj._id);
                    
                        var groupId = request.request_id;

                        let members = [];
                        
                        members.push("system");

                        // qui errore participants sembra 0,1 object ???
                        request.participants.forEach(function(participant,index) {
                            members.push(participant);
                        });
                        // requestObj.participants;
                        // var members = reqParticipantArray;

                        if (request.lead) {
                            // lead_id used. Change it?
                            members.push(request.lead.lead_id);
                        }
                        winston.verbose("Chat21 group with members for request.participants.update: " , members);  

                         //setMembers: function(members, group_id){
                                //racecondition  
                        chat21.groups.setMembers(members, groupId).then(function(data) {
                                winston.verbose("Chat21 group set for request.participants.update : " + JSON.stringify(data));      
                                chat21Event.emit('group.setMembers', data);                                          
                            }).catch(function(err) {
                                winston.error("Error setting chat21 group for request.participants.update ", err);
                                chat21Event.emit('group.setMembers.error', err);
                            });


                        // let oldParticipants = data.beforeRequest.participants;
                        // winston.info("oldParticipants ", oldParticipants);

                        // let newParticipants = data.request.participants;
                        // winston.info("newParticipants ", newParticipants);

                        // var removedParticipants = oldParticipants.filter(d => !newParticipants.includes(d));
                        // winston.info("removedParticipants ", removedParticipants);

                       

                        removedParticipants.forEach(function(removedParticipant) {
                            winston.debug("removedParticipant ", removedParticipant);

                            // archive: function(recipient_id, user_id){
                            //racecondition?low it's not dangerous archive a conversation that doen't exist

                            chat21.conversations.archive(request.request_id, removedParticipant)
                            .then(function(data){
                                winston.verbose("Chat21 archived "+ JSON.stringify(data));
                        
                                chat21Event.emit('conversation.archived', data);                                               
        
                                }).catch(function(err) {
                                    winston.error("Chat21 archived err", err);
                                    chat21Event.emit('conversation.archived.error', err);
                                });

                        });
                        



                    }
                });
            });
            
            
               requestEvent.on('request.participants.join',  function(data) {       
                   winston.debug("chat21Handler requestEvent request.participants.join called" , data);

                   let request = data.request;
                   let member = data.member;

                setImmediate(() => {
                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {

                        chat21.auth.setAdminToken(adminToken);

                        

                     
                        // let requestObj = request.toJSON();
                        
                        var groupId = request.request_id;

                        winston.verbose("joining member " + member +" for chat21 group with request : " + groupId);
                                            
                             //racecondition?

                         //join: function(member_id, group_id){
                        chat21.groups.join(member, groupId).then(function(data) {
                                winston.verbose("Chat21 group joined: " + JSON.stringify(data));      
                                chat21Event.emit('group.join', data);                                          
                            }).catch(function(err) {
                                winston.error("Error joining chat21 group ", err);
                                chat21Event.emit('group.join.error', err);
                            });



                    }
                });
            });
            
            
               requestEvent.on('request.participants.leave',  function(data) {     
                   winston.debug("chat21Handler requestEvent request.participants.leave called" , data);

                   let request = data.request;
                   let member = data.member;

                setImmediate(() => {
                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {

                        chat21.auth.setAdminToken(adminToken);

                     

                     
                        // let requestObj = request.toJSON();
                        
                        var groupId = request.request_id;

                        winston.verbose("leaving " + member +" for chat21 group for request with id: " + groupId);
                                   
                        //racecondition?
                         //leave: function(member_id, group_id){
                        chat21.groups.leave(member, groupId).then(function(data) {
                                winston.verbose("Chat21 group leaved: " + JSON.stringify(data));      
                                chat21Event.emit('group.leave', data);                                          
                            }).catch(function(err) {
                                winston.error("Error leaving chat21 group ", err);
                                chat21Event.emit('group.leave.error', err);
                            });


                            // anche devi archiviare la conversazione per utente corrente 
                            //racecondition?
                            chat21.conversations.archive(request.request_id, member)
                            .then(function(data){
                                winston.verbose("Chat21 archived " + JSON.stringify(data));
                        
                                chat21Event.emit('conversation.archived', data);                                               
     
                             }).catch(function(err) {
                                 winston.error("Chat21 archived err", err);
                                 chat21Event.emit('conversation.archived.error', err);
                             });

                           


                    }
                });
            })
            




            groupEvent.on('group.create',  function(group) {                       

                if (process.env.SYNC_CHAT21_GROUPS !=="true") {
                    winston.debug("Sync Tiledesk to Chat21 groups disabled");
                    return;
                }

                winston.verbose("Creating chat21 group", group);
                
                setImmediate(() => {

                    chat21.auth.setAdminToken(adminToken);


                    var groupMembers = group.members;
                    winston.debug("groupMembers ", groupMembers); 
                    
                    var group_id = "group-" + group._id;
                    winston.debug("group_id :" + group_id); 

                    return chat21.groups.create(group.name, groupMembers, undefined, group_id).then(function(data) {
                        winston.verbose("Chat21 group created: " + JSON.stringify(data));      
                        // TODO ritorna success anche se 
                        // verbose: Chat21 group created: {"success":false,"err":{"message":"Channel closed","stack":"IllegalOperationError: Channel closed\n    at ConfirmChannel.<anonymous> (/usr/src/app/node_modules/amqplib/lib/channel.js:160:11)\n    at ConfirmChannel.Channel.publish (/usr/src/app/node_modules/amqplib/lib/callback_model.js:171:17)\n    at ConfirmChannel.publish (/usr/src/app/node_modules/amqplib/lib/callback_model.js:301:36)\n    at Chat21Api.publish (/usr/src/app/chat21Api/index.js:1028:29)\n    at Chat21Api.sendMessageRaw (/usr/src/app/chat21Api/index.js:762:14)\n    at Chat21Api.sendGroupWelcomeMessage (/usr/src/app/chat21Api/index.js:205:14)\n    at /usr/src/app/chat21Api/index.js:99:22\n    at /usr/src/app/chat21Api/index.js:234:17\n    at /usr/src/app/chatdb/index.js:77:9\n    at executeCallback (/usr/src/app/node_modules/mongodb/lib/operations/execute_operation.js:70:5)\n    at updateCallback (/usr/src/app/node_modules/mongodb/lib/operations/update_one.js:41:3)\n    at /usr/src/app/node_modules/mongodb/lib/operations/update_one.js:24:64\n    at handleCallback (/usr/src/app/node_modules/mongodb/lib/utils.js:128:55)\n    at /usr/src/app/node_modules/mongodb/lib/operations/common_functions.js:378:5\n    at handler (/usr/src/app/node_modules/mongodb/lib/core/sdam/topology.js:913:24)","stackAtStateChange":"Stack capture: Socket error\n    at Connection.C.onSocketError (/usr/src/app/node_modules/amqplib/lib/connection.js:354:13)\n    at Connection.emit (events.js:314:20)\n    at Socket.go (/usr/src/app/node_modules/amqplib/lib/connection.js:481:12)\n    at Socket.emit (events.js:314:20)\n    at emitReadable_ (_stream_readable.js:557:12)\n    at processTicksAndRejections (internal/process/task_queues.js:83:21)"}}
                        chat21Event.emit('group.create', data);                                          
                    }).catch(function(err) {
                        winston.error("Error creating chat21 group ", err);
                        chat21Event.emit('group.create.error', err);
                    });

                });

             });


             groupEvent.on('group.update',  function(group) {                       

                if (process.env.SYNC_CHAT21_GROUPS !=="true") {
                    winston.debug("Sync Tiledesk to Chat21 groups disabled");
                    return;
                }

                winston.verbose("Updating chat21 group", group);
                
                setImmediate(() => {

                    chat21.auth.setAdminToken(adminToken);


                    var groupMembers = group.members;
                    winston.debug("groupMembers ", groupMembers); 
                    
                    var group_id = "group-" + group._id;
                    winston.debug("group_id :" + group_id); 

                            // update: function(name, owner, attributes, group_id){
                    return chat21.groups.update(group.name, undefined, undefined, group_id).then(function(data) {
                        winston.verbose("Chat21 group.update updated: " + JSON.stringify(data));      
                        chat21Event.emit('group.update', data);     
                        return chat21.groups.setMembers(groupMembers, group_id).then(function(data) {      
                            winston.verbose("Chat21 group.update set: " + JSON.stringify(data));      
                            chat21Event.emit('group.setMembers', data);          
                        }).catch(function(err) {
                            winston.error("Error setMembers chat21 group.update ", err);
                            chat21Event.emit('group.setMembers.error', err);
                        });                             
                    }).catch(function(err) {
                        winston.error("Error setting chat21 group.update ", err);
                        chat21Event.emit('group.update.error', err);
                    });

                });

             });





             groupEvent.on('group.delete',  function(group) {                       

                if (process.env.SYNC_CHAT21_GROUPS !=="true") {
                    winston.debug("Sync Tiledesk to Chat21 groups disabled");
                    return;
                }

                winston.verbose("Deleting chat21 group for group.delete", group);
                
                setImmediate(() => {

                    chat21.auth.setAdminToken(adminToken);
                  
                    var group_id = "group-" + group._id;
                    winston.debug("group_id :" + group_id); 

                    //Remove members but group remains.

                    return chat21.groups.setMembers(["system"], group_id).then(function(data) {      
                        winston.verbose("Chat21 group set for group.delete : " + JSON.stringify(data));      
                        chat21Event.emit('group.setMembers', data);          
                    }).catch(function(err) {
                        winston.error("Error setMembers chat21 group for group.delete", err);
                        chat21Event.emit('group.setMembers.error', err);
                    });           

                });

             });

    }

    
}

var chat21Handler = new Chat21Handler();
module.exports = chat21Handler;


================================================
FILE: channels/chat21/chat21Util.js
================================================



// ***** UNUSED
class Chat21Util {
    
    getButtonFromText(message, bot, qna) { 
            var that = this;
            var text = message.text;
            return new Promise(function(resolve, reject) {
    
                var repl_message = Object.assign({}, message);
               
                // cerca i bottoni eventualmente definiti
                var button_pattern = /^\*.*/mg; // buttons are defined as a line starting with an asterisk
                var text_buttons = text.match(button_pattern);


                var image_pattern = /^\\image:.*/mg; 
                var imagetext = text.match(image_pattern);

                var webhook_pattern = /^\\webhook:.*/mg; 
                var webhooktext = text.match(webhook_pattern);


                if (text_buttons) {
                    var text_with_removed_buttons = text.replace(button_pattern,"").trim();
                    repl_message.text = text_with_removed_buttons
                    var buttons = []
                    text_buttons.forEach(element => {
                    // console.log("button ", element)
                    var remove_extra_from_button = /^\*/mg;
                    var button_text = element.replace(remove_extra_from_button, "").trim()
                    var button = {}
                    button["type"] = "text"
                    button["value"] = button_text
                    buttons.push(button)
                    });
                    repl_message.attributes =
                    { 
                    attachment: {
                        type:"template",
                        buttons: buttons
                    }
                    }
                    repl_message.type = "text";            
    
                
                }  else if (imagetext && imagetext.length>0) {
                    var imageurl = imagetext[0].replace("\\image:","").trim();
                    // console.log("imageurl ", imageurl)
                    var text_with_removed_image = text.replace(image_pattern,"").trim();
                    repl_message.text = text_with_removed_image + " " + imageurl
                    repl_message.metadata = {src: imageurl, width:200, height:200};
                    repl_message.type = "image";
                }
    
    
               
                else if (webhooktext && webhooktext.length>0) {
                    var webhookurl = webhooktext[0].replace("\\webhook:","").trim();
                    // console.log("webhookurl ", webhookurl)
    
                    return request({                        
                        uri :  webhookurl,
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        method: 'POST',
                        json: true,
                        body: {text: text, bot: bot, message: message, qna: qna},
                        }).then(response => {
                            if (response.statusCode >= 400) {                  
                                return reject(`HTTP Error: ${response.statusCode}`);
                            }
                            // console.log("webhookurl repl_message ", response);
                            that.getButtonFromText(response.text,message, bot,qna).then(function(bot_answer) {
                                return resolve(bot_answer);
                            });
                        });
                 
                }else {
                    // console.log("repl_message ", repl_message)
                    return resolve(repl_message);
                }
    
    
               
            });
        }


// move to messageAction  no create a new module messageInterpreter??




        findSplits(result) {
            var commands = []
            const text = result['fulfillmentText'] // "parte 1\\splittesto12\\split\npt2.capone detto\\split:4000\npt.3. muggio\\split\npt. 4.Andtonino Mustacchio"
            // const text = "parte 1NO\\splittesto12\\split\npt2.capone detto\\split:4000\npt.3. muggio\\split\npt. 4.Dammi la tua email"
            const split_pattern = /^(\\split[:0-9]*)/mg //ex. \split:500
            var parts = text.split(split_pattern)
            for (var i=0; i < parts.length; i++) {
                let p = parts[i]
                console.log("part: " + p)
                if (i % 2 != 0) {
                // split command
                console.log("split command: " + p)
                var split_parts = p.split(":")
                var wait_time = 1000
                if (split_parts.length == 2) {
                    wait_time = split_parts[1]
                }
                console.log("wait time: " + wait_time)
                var command = {}
                command.type = "wait"
                command.time = parseInt(wait_time, 10)
                commands.push(command)
                }
                else {
                // message command
                var command = {}
                command.type = "message"
                command.text = p.trim()
                commands.push(command)
                if ( i == parts.length -1 &&
                    result['fulfillmentMessages'] &&
                    result['fulfillmentMessages'][1] &&
                    result['fulfillmentMessages'][1].payload) {
                    command.payload = result['fulfillmentMessages'][1].payload
                }
                }
            }
            commands.forEach(c => {
                console.log("* * * * * * * * * command: ", c)
            })
            return commands
        }
    
        parseReply(text) {
            
    
            let TEXT_KEY = 'text'
    
            let TYPE_KEY = 'type'
            let ATTRIBUTES_KEY = 'attributes'
            let METADATA_KEY = "metadata"
            let TYPE_IMAGE = 'image'
    
            var reply = {}
          
            console.log("TEXT: ", text)
            reply[TEXT_KEY] = text
            reply[ATTRIBUTES_KEY] = null
          
            // looks for images
            var image_pattern = /^\\image:.*/mg; // images are defined as a line starting with \image:IMAGE_URL
            // console.log("Searching images with image_pattern: ", image_pattern)
            var images = text.match(image_pattern);
            // console.log("images: ", images)
            if (images && images.length > 0) {
              const image_text = images[0]
              var text = text.replace(image_text,"").trim()
              const image_url = image_text.replace("\\image:", "")
              reply[TEXT_KEY] = text
              reply[TYPE_KEY] = TYPE_IMAGE
              reply[METADATA_KEY] = {
                src: image_url,
                width: 200,
                height: 200
              }
            }
          
            // looks for bullet buttons
            var button_pattern = /^\*.*/mg; // button pattern is a line that starts with *TEXT_OF_BUTTON (every button on a line)
            var text_buttons = text.match(button_pattern);
            if (text_buttons) {
              // ricava il testo rimuovendo i bottoni
              var text_with_removed_buttons = text.replace(button_pattern,"").trim();
              reply[TEXT_KEY] = text_with_removed_buttons
              // estrae i bottoni
              var buttons = []
              text_buttons.forEach(element => {
                var remove_extra_from_button = /^\*/mg; // removes initial "*"
                var button_text = element.replace(remove_extra_from_button, "").trim()
                var button = {}
                button[TYPE_KEY] = "text"
                button["value"] = button_text
                buttons.push(button)
                console.log("Added button: " + button_text)
              });
              if (reply[ATTRIBUTES_KEY] == null) {
                reply[ATTRIBUTES_KEY] = {}
              }
              reply[ATTRIBUTES_KEY]["attachment"] = {
                type:"template",
                buttons: buttons
              }
            }
            return reply
        }


        
}

var chat21Util = new Chat21Util();
module.exports = chat21Util;

================================================
FILE: channels/
Download .txt
gitextract_qh16oa6j/

├── .circleci/
│   └── config.yml
├── .dockerignore
├── .firebasekey.json.enc
├── .github/
│   └── workflows/
│       ├── docker-community-profiler-latest.yml
│       ├── docker-community-push-latest.yml
│       ├── docker-community-worker-push-latest.yml
│       ├── docker-image-en-tag-push.yml
│       ├── docker-image-tag-community-tag-push.yml
│       ├── docker-image-tag-worker-community-tag-push.yml
│       └── docker-push-en-push-latest.yml
├── .gitignore
├── .glitch-assets
├── .npmrc_
├── .travis.yml
├── CHANGELOG.md
├── Dockerfile
├── Dockerfile-en
├── Dockerfile-jobs
├── Dockerfile-profiler
├── LICENSE
├── README.md
├── app.js
├── app.json
├── archive.sh
├── bin/
│   └── www
├── channels/
│   ├── channelManager.js
│   └── chat21/
│       ├── chat21Client.js
│       ├── chat21Config.js
│       ├── chat21Contact.js
│       ├── chat21Event.js
│       ├── chat21Handler.js
│       ├── chat21Util.js
│       ├── chat21WebHook.js
│       ├── configRoute.js
│       ├── firebaseConfig.js
│       ├── firebaseConnector.js
│       ├── firebaseService.js
│       ├── firebaseauth.js
│       ├── nativeauth.js
│       ├── package.json
│       ├── test-int/
│       │   ├── chat21Handler.js
│       │   └── chat21WebHook.js
│       └── tiledesk-util.js
├── config/
│   ├── cache.js
│   ├── database.js
│   ├── email.js
│   ├── global.js
│   ├── kb/
│   │   ├── embedding.js
│   │   ├── engine.hybrid.js
│   │   ├── engine.js
│   │   ├── prompt/
│   │   │   └── rag/
│   │   │       ├── PromptManager.js
│   │   │       ├── general.txt
│   │   │       ├── gpt-3.5.txt
│   │   │       ├── gpt-4.1.txt
│   │   │       ├── gpt-4.txt
│   │   │       ├── gpt-4o.txt
│   │   │       ├── gpt-5.txt
│   │   │       └── gpt-5.x.txt
│   │   └── situatedContext.js
│   ├── labels/
│   │   └── widget.json
│   ├── widget.js
│   ├── winston-mt-multilogger.js
│   ├── winston-mt.js
│   └── winston.js
├── deploy-beta.sh
├── deploy.sh
├── deploynew.sh
├── docs/
│   ├── api-dev.md
│   ├── api-mgm.md
│   ├── deploy.md
│   ├── performance.md
│   ├── routes-answered.md
│   ├── testing.md
│   └── upgrading.md
├── errorCodes.js
├── event/
│   ├── authEvent.js
│   ├── botEvent.js
│   ├── connectionEvent.js
│   ├── departmentEvent.js
│   ├── emailEvent.js
│   ├── faqBotEvent.js
│   ├── groupEvent.js
│   ├── integrationEvent.js
│   ├── labelEvent.js
│   ├── leadEvent.js
│   ├── message2Event.js
│   ├── messageEvent.js
│   ├── messagePromiseEvent.js
│   ├── projectEvent.js
│   ├── projectUserEvent.js
│   ├── requestEvent.js
│   ├── roleEvent.js
│   └── subscriptionEvent.js
├── jobs.js
├── jobsManager.js
├── middleware/
│   ├── fetchLabels.js
│   ├── file-type.js
│   ├── has-role.js
│   ├── ipFilter.js
│   ├── noentitycheck.js
│   ├── passport.js
│   ├── recaptcha.js
│   └── valid-token.js
├── migrations/
│   ├── 1601628781595-project_users_presence.js
│   ├── 1602847963299-message-channel_type-and-channel-fields-added--autosync.js
│   ├── 1603797978971-project_users-status-field-added--autosync.js
│   ├── 1603955232377-requests-channel-outbound-fields--autosync.js
│   ├── 1603955232378-requests-channel-fields--autosync.js
│   ├── 1604082287722-labels-data-default-fields--autosync.js
│   ├── 1604082288723-labels-waiting_time-added_suffix_reply_time--autosync.js
│   ├── 1611576399823-project-settings-max_agent_assigned_renamed--autosync.js
│   ├── 1611576534533-project_user-max_assigned_chat-renamed--autosync.js
│   ├── 1615214914082-faq-intent_id-intent_display_name-fields-added--autosync.js
│   ├── 1616685902635-request_agents_to_snapshot_agents--autosync.js
│   ├── 1616687831941-trigger_availableAgentsCount_to_snapshot_agents--autosync.js
│   ├── 1616687831941-trigger_availableAgentsCount_to_snapshot_agents-key--autosync.js
│   ├── 1619185894304-request-remove-duplicated-request-by-request_id--autosync.js
│   ├── 1752742733903-namespace-engine-migration.js
│   ├── 1757601159298-project_user_role_type.js
│   └── 1771844588961-phone-channels-migration.js
├── models/
│   ├── actionsConstants.js
│   ├── analyticMessagesResult.js
│   ├── analyticProject_usersResult.js
│   ├── analyticResult.js
│   ├── analytics.js
│   ├── auth.js
│   ├── bot.1.js
│   ├── channel.js
│   ├── channelConstants.js
│   ├── chatbotTemplates.js
│   ├── chatbotTypes.js
│   ├── contact.js
│   ├── department.js
│   ├── faq.js
│   ├── faq_kb.js
│   ├── firebaseSetting.js
│   ├── flowLogs.js
│   ├── group.js
│   ├── groupMemberSchama.js
│   ├── integrations.js
│   ├── kbConstants.js
│   ├── kb_setting.js
│   ├── label.js
│   ├── labelSingle.js
│   ├── lead.js
│   ├── leadConstants.js
│   ├── location.js
│   ├── message.js
│   ├── messageConstants.js
│   ├── note.js
│   ├── openai_kbs.js
│   ├── pending-invitation.js
│   ├── permissionConstants.js
│   ├── presence.js
│   ├── profile.js
│   ├── project.js
│   ├── project_user.js
│   ├── projectf.js
│   ├── property.js
│   ├── request.js
│   ├── requestConstants.js
│   ├── requestSnapshot.js
│   ├── requestStatus.js
│   ├── requester.js
│   ├── role.js
│   ├── roleConstants.js
│   ├── routerLogger.js
│   ├── routingConstants.js
│   ├── segment.js
│   ├── setting.js
│   ├── subscription.js
│   ├── subscriptionLog.js
│   ├── tag.js
│   ├── tagLibrary.js
│   ├── transaction.js
│   ├── user.js
│   ├── webhook.js
│   └── whatsappLog.js
├── package.json
├── public/
│   ├── loaderio-e6d5fbf72a45848fe2f43f7a986a1103.txt
│   ├── samples/
│   │   └── bot/
│   │       └── external/
│   │           └── searcher/
│   │               └── parse
│   ├── stylesheets/
│   │   └── style.css
│   └── wstest/
│       ├── TB.js
│       ├── index.html
│       └── tilebase.js
├── publiccode.yml
├── pubmodules/
│   ├── activities/
│   │   ├── activityArchiver.js
│   │   ├── index.js
│   │   ├── models/
│   │   │   └── activity.js
│   │   ├── routes/
│   │   │   └── activity.js
│   │   └── test/
│   │       └── activityRoute.js
│   ├── analytics/
│   │   ├── analytics.js
│   │   └── index.js
│   ├── apps/
│   │   ├── index.js
│   │   └── listener.js
│   ├── cache/
│   │   ├── index.js
│   │   └── mongoose-cachegoose-fn.js
│   ├── canned/
│   │   ├── cannedResponse.js
│   │   ├── cannedResponseRoute.js
│   │   └── index.js
│   ├── chatbotTemplates/
│   │   ├── index.js
│   │   └── listener.js
│   ├── dialogflow/
│   │   ├── index.js
│   │   └── listener.js
│   ├── emailNotification/
│   │   ├── index.js
│   │   └── requestNotification.js
│   ├── entityEraser/
│   │   ├── eraserInterceptor.js
│   │   └── index.js
│   ├── events/
│   │   ├── analyticEventsResult.js
│   │   ├── event.js
│   │   ├── event2Event.js
│   │   ├── eventEvent.js
│   │   ├── eventRoute.js
│   │   ├── eventService.js
│   │   ├── index.js
│   │   └── test/
│   │       └── eventRoute.js
│   ├── kaleyra/
│   │   ├── index.js
│   │   └── listener.js
│   ├── messageActions/
│   │   ├── event/
│   │   │   └── messageActionEvent.js
│   │   ├── index.js
│   │   └── messageActionsInterceptor.js
│   ├── messageTransformer/
│   │   ├── index.js
│   │   ├── messageHandlebarsTransformerInterceptor.js
│   │   ├── messageTransformerInterceptor.js
│   │   ├── microLanguageAttributesTransformerInterceptor.js
│   │   └── microLanguageTransformerInterceptor.js
│   ├── messenger/
│   │   ├── index.js
│   │   └── listener.js
│   ├── mqttTest/
│   │   ├── index.js
│   │   └── listener.js
│   ├── pubModulesManager.js
│   ├── queue/
│   │   ├── index.js
│   │   ├── reconnect.js
│   │   └── reconnectFanout.js
│   ├── rasa/
│   │   ├── index.js
│   │   └── listener.js
│   ├── routing-queue/
│   │   ├── index.js
│   │   ├── listener.js
│   │   └── listenerQueued.js
│   ├── rules/
│   │   ├── appRules.js
│   │   └── conciergeBot.js
│   ├── s/
│   │   ├── index.js
│   │   ├── models/
│   │   │   └── subscription-payment.js
│   │   └── stripe/
│   │       └── index.js
│   ├── scheduler/
│   │   ├── index.js
│   │   ├── taskRunner.js
│   │   └── tasks/
│   │       ├── closeAgentUnresponsiveRequestTask.js
│   │       ├── closeBotUnresponsiveRequestTask.js
│   │       └── requestTaskSchedulerAgenda.js
│   ├── sms/
│   │   ├── index.js
│   │   └── listener.js
│   ├── telegram/
│   │   ├── index.js
│   │   └── listener.js
│   ├── tilebot/
│   │   ├── index.js
│   │   └── listener.js
│   ├── trigger/
│   │   ├── default.js
│   │   ├── event/
│   │   │   ├── actionEventEmitter.js
│   │   │   ├── flowEventEmitter.js
│   │   │   └── triggerEventEmitter.js
│   │   ├── index.js
│   │   ├── models/
│   │   │   └── trigger.js
│   │   ├── rulesTrigger.js
│   │   ├── start.js
│   │   └── triggerRoute.js
│   ├── voice/
│   │   ├── index.js
│   │   └── listener.js
│   ├── voice-twilio/
│   │   ├── index.js
│   │   └── listener.js
│   └── whatsapp/
│       ├── index.js
│       └── listener.js
├── routes/
│   ├── admin.js
│   ├── answered.js
│   ├── auth.js
│   ├── authtest.js
│   ├── authtestWithRoleCheck.js
│   ├── campaigns.js
│   ├── copilot.js
│   ├── department.js
│   ├── email.js
│   ├── faq.js
│   ├── faq_kb.js
│   ├── faqpub.js
│   ├── files.js
│   ├── filesp.js
│   ├── group.js
│   ├── images.js
│   ├── index.js
│   ├── integration.js
│   ├── jwt.js
│   ├── kb.js
│   ├── kbsettings.js
│   ├── key.js
│   ├── labels-no-default.js
│   ├── labels.js
│   ├── labelsSingle.js
│   ├── lead.js
│   ├── llm.js
│   ├── logs.js
│   ├── mcp.js
│   ├── message.js
│   ├── messagesRoot.js
│   ├── openai.js
│   ├── pending-invitation.js
│   ├── project.js
│   ├── project_user.js
│   ├── project_user_test.js
│   ├── property.js
│   ├── public-analytics.js
│   ├── public-request.js
│   ├── quotes.js
│   ├── request.js
│   ├── requestUtilRoot.js
│   ├── roles.js
│   ├── segment.js
│   ├── setting.js
│   ├── subscription.js
│   ├── tag.js
│   ├── troubleshooting.js
│   ├── unanswered.js
│   ├── urls.js
│   ├── user-request.js
│   ├── users-util.js
│   ├── users.js
│   ├── webhook.js
│   ├── webhooks.js
│   ├── widget.js
│   └── widgetLoader.js
├── services/
│   ├── BotSubscriptionNotifier.js
│   ├── QuoteManager.js
│   ├── RateManager.js
│   ├── Scheduler.js
│   ├── aiManager.js
│   ├── aiReindexService.js
│   ├── aiService.js
│   ├── banUserNotifier.js
│   ├── bootDataLoader.js
│   ├── cacheEnabler.js
│   ├── chatbotService.js
│   ├── departmentService.js
│   ├── emailService.js
│   ├── faqBotHandler.js
│   ├── faqBotSupport.js
│   ├── faqService.js
│   ├── fileGridFsService.js
│   ├── fileService.js
│   ├── filesystemService.js
│   ├── geoService.js
│   ├── integrationService.js
│   ├── labelService-no-default.js
│   ├── labelService.js
│   ├── leadService.js
│   ├── logsService.js
│   ├── mcpService.js
│   ├── messageService.js
│   ├── modulesManager.js
│   ├── mongoose-cache-fn.js
│   ├── mongoose-cache.js
│   ├── operatingHoursService.js
│   ├── pendingInvitationService.js
│   ├── projectService.js
│   ├── projectUserService.js
│   ├── requestService.js
│   ├── schemaMigrationService.js
│   ├── settingDataLoader.js
│   ├── subscriptionNotifier.js
│   ├── subscriptionNotifierQueued.js
│   ├── testWsService.js
│   ├── trainingService.js
│   ├── updateLeadQueued.js
│   ├── updateRequestSnapshotQueued.js
│   ├── userService.js
│   └── webhookService.js
├── template/
│   ├── chatbot/
│   │   ├── blank.js
│   │   ├── blank_copilot.js
│   │   ├── blank_voice.js
│   │   ├── blank_voice_twilio.js
│   │   ├── blank_webhook.js
│   │   ├── empty.js
│   │   ├── example.js
│   │   ├── handoff.js
│   │   ├── index.js
│   │   └── official_copilot.js
│   └── email/
│       ├── assignedEmailMessage.html
│       ├── assignedRequest.html
│       ├── beenInvitedExistingUser.html
│       ├── beenInvitedNewUser.html
│       ├── checkpointReachedEmail.html
│       ├── emailDirect.html
│       ├── newMessage.html
│       ├── newMessageFollower.html
│       ├── passwordChanged.html
│       ├── pooledEmailMessage.html
│       ├── pooledRequest.html
│       ├── redirectToDesktopEmail.html
│       ├── redirectToDesktopEmail_new.html
│       ├── resetPassword.html
│       ├── sendTranscript.html
│       ├── test.html
│       ├── ticket.html
│       ├── ticket.txt
│       └── verify.html
├── test/
│   ├── app-test.js
│   ├── authentication.js
│   ├── authenticationJwt.js
│   ├── authorization.js
│   ├── campaignsRoute.js
│   ├── cannedRoute.js
│   ├── dateUtils.test.js
│   ├── departmentService.js
│   ├── emailService.js
│   ├── faqRoute.js
│   ├── faqService.js
│   ├── faqkbRoute.js
│   ├── fileFilter.test.js
│   ├── fileRoute.js
│   ├── filepRoute.js
│   ├── fixtures/
│   │   ├── TooManykbUrlsList.txt
│   │   ├── example-faqs.csv
│   │   ├── example-json-intents.txt
│   │   ├── example-json-multiple-operation-mock.js
│   │   ├── example-json-rules.txt
│   │   ├── example-json.txt
│   │   ├── example-kb-faqs.csv
│   │   ├── example-webhook-json.txt
│   │   ├── example.json
│   │   ├── exported_namespace.json
│   │   ├── kbUrlsList.txt
│   │   └── sample.xyz
│   ├── imageRoute.js
│   ├── jwtRoute.js
│   ├── kbRoute.js
│   ├── kbsettingsRoute.js
│   ├── keysRoute.js
│   ├── labelRoute.js
│   ├── labelService.js
│   ├── leadRoute.js
│   ├── leadService.js
│   ├── logsRoute.js
│   ├── messageRootRoute.js
│   ├── messageRoute-newjwt._js
│   ├── messageRoute.js
│   ├── messageService.js
│   ├── mock/
│   │   ├── chatbotMock.js
│   │   ├── emailMock.js
│   │   ├── messageMock.js
│   │   ├── projectMock.js
│   │   ├── requestMock.js
│   │   └── tdCacheMock.js
│   ├── openaiRoute.js
│   ├── projectRoute.js
│   ├── projectService.js
│   ├── projectUserRoute.js
│   ├── quoteManager.js
│   ├── requestRoute.js
│   ├── requestService.js
│   ├── userRequestRoute.js
│   ├── userRoute.js
│   ├── userService.js
│   └── webhookRoute.js
├── test-int/
│   ├── bot.js
│   ├── botSubscriptionNotifier.js
│   ├── cache-project.js
│   └── cache-project_user.js
├── tiledesk-jmeter.jmx
├── utils/
│   ├── TdCache.js
│   ├── UIDGenerator.js
│   ├── aiUtils.js
│   ├── arrayUtil.js
│   ├── autoIncrement.js
│   ├── botFromParticipant.js
│   ├── cacheUtil.js
│   ├── commons/
│   │   ├── extend-query.js
│   │   ├── q1.js
│   │   └── testperformance.js
│   ├── connectionUtil.js
│   ├── datesUtil.js
│   ├── fileUtils.js
│   ├── httpUtil.js
│   ├── i8nUtil.js
│   ├── jobs-worker-queue-manager/
│   │   ├── JobManagerV2.js
│   │   └── queueManagerClassV2.js
│   ├── orgUtil.js
│   ├── phoneUtil.js
│   ├── project_userUtil.js
│   ├── promiseUtil.js
│   ├── recipientEmailUtil.js
│   ├── requestUtil.js
│   ├── segment2mongoConverter.js
│   ├── sendEmailUtil.js
│   ├── sendMessageUtil.js
│   ├── stringUtil.js
│   ├── userUtil.js
│   └── winston-mongodb/
│       ├── helpers.js
│       ├── winston-mongodb.d.ts
│       └── winston-mongodb.js
├── views/
│   ├── admin-get.jade
│   ├── admin-saved-get.jade
│   ├── error.jade
│   ├── index.jade
│   ├── layout-admin.jade
│   ├── layout.jade
│   ├── messages.jade
│   └── messages_old.jade
└── websocket/
    ├── pubsub.js
    ├── subscription.js
    └── webSocketServer.js
Download .txt
SYMBOL INDEX (810 symbols across 194 files)

FILE: app.js
  constant JSON_BODY_LIMIT (line 298) | const JSON_BODY_LIMIT = process.env.JSON_BODY_LIMIT || '500KB';
  constant WEBHOOK_BODY_LIMIT (line 301) | const WEBHOOK_BODY_LIMIT = process.env.WEBHOOK_BODY_LIMIT || '5mb';

FILE: channels/channelManager.js
  class ChannelManager (line 23) | class ChannelManager {
    method use (line 25) | use(app) {
    method useUnderProjects (line 58) | useUnderProjects(app) {
    method listen (line 62) | listen() {

FILE: channels/chat21/chat21Event.js
  class Chat21Event (line 3) | class Chat21Event extends EventEmitter {}

FILE: channels/chat21/chat21Handler.js
  class Chat21Handler (line 53) | class Chat21Handler {
    method typing (line 56) | typing(message, timestamp) {
    method listen (line 74) | listen() {

FILE: channels/chat21/chat21Util.js
  class Chat21Util (line 5) | class Chat21Util {
    method getButtonFromText (line 7) | getButtonFromText(message, bot, qna) {
    method findSplits (line 98) | findSplits(result) {
    method parseReply (line 141) | parseReply(text) {

FILE: channels/chat21/firebaseService.js
  class FirebaseService (line 6) | class FirebaseService {
    method createCustomToken (line 8) | createCustomToken(uid) {
    method createCustomTokenWithAttribute (line 21) | createCustomTokenWithAttribute(uid, customAttributes) {

FILE: channels/chat21/tiledesk-util.js
  class TiledeskUtil (line 7) | class TiledeskUtil {
    method findSplits (line 20) | findSplits(text) {
    method parseReply (line 58) | parseReply(text) {

FILE: config/kb/prompt/rag/PromptManager.js
  class PromptManager (line 26) | class PromptManager {
    method constructor (line 28) | constructor(basePath) {
    method getPrompt (line 33) | getPrompt(name) {

FILE: config/winston-mt-multilogger.js
  function getLogger (line 9) | function getLogger(moduleName) {
  function createNewLogger (line 24) | function createNewLogger(moduleName) {

FILE: event/authEvent.js
  class AuthEvent (line 4) | class AuthEvent extends EventEmitter {
    method constructor (line 5) | constructor() {

FILE: event/botEvent.js
  class BotEvent (line 11) | class BotEvent extends EventEmitter {
    method constructor (line 13) | constructor() {
    method listen (line 20) | listen() {
  function getBotFromParticipants (line 189) | function getBotFromParticipants(participants) {
  function getBotId (line 212) | function getBotId(message) {

FILE: event/connectionEvent.js
  class ConnectionEvent (line 3) | class ConnectionEvent extends EventEmitter {}

FILE: event/departmentEvent.js
  class DepartmentEvent (line 3) | class DepartmentEvent extends EventEmitter {}

FILE: event/emailEvent.js
  class EmailEvent (line 7) | class EmailEvent extends EventEmitter {
    method constructor (line 8) | constructor() {
    method listen (line 13) | listen() {

FILE: event/faqBotEvent.js
  class FaqBotEvent (line 3) | class FaqBotEvent extends EventEmitter {}

FILE: event/groupEvent.js
  class GroupEvent (line 3) | class GroupEvent extends EventEmitter {}

FILE: event/integrationEvent.js
  class IntegrationEvent (line 5) | class IntegrationEvent extends EventEmitter {
    method constructor (line 6) | constructor() {

FILE: event/labelEvent.js
  class LabelEvent (line 3) | class LabelEvent extends EventEmitter {}

FILE: event/leadEvent.js
  class LeadEvent (line 3) | class LeadEvent extends EventEmitter {
    method constructor (line 4) | constructor() {

FILE: event/message2Event.js
  class Message2Event (line 10) | class Message2Event extends EventEmitter2 {}

FILE: event/messageEvent.js
  class MessageEvent (line 14) | class MessageEvent extends EventEmitter {
    method constructor (line 15) | constructor() {
  function emitCompleteMessage (line 25) | function emitCompleteMessage(message) {
  function populateMessageCreate (line 42) | function populateMessageCreate(message) {
  function populateMessageUpdate (line 45) | function populateMessageUpdate(message) {
  function populateMessageWithRequest (line 53) | function populateMessageWithRequest(message, eventPrefix) {

FILE: event/messagePromiseEvent.js
  class MessagePromiseEvent (line 3) | class MessagePromiseEvent extends EventEmitter {}

FILE: event/projectEvent.js
  class ProjectEvent (line 3) | class ProjectEvent extends EventEmitter {}

FILE: event/projectUserEvent.js
  class ProjectUserEvent (line 5) | class ProjectUserEvent extends EventEmitter {
    method constructor (line 6) | constructor() {
    method registerListeners (line 11) | registerListeners() {

FILE: event/requestEvent.js
  class RequestEvent (line 7) | class RequestEvent extends EventEmitter {
    method constructor (line 8) | constructor() {

FILE: event/roleEvent.js
  class RoleEvent (line 3) | class RoleEvent extends EventEmitter {}

FILE: event/subscriptionEvent.js
  class SubscriptionEvent (line 3) | class SubscriptionEvent extends EventEmitter {}

FILE: jobs.js
  function main (line 66) | async function main()
  function panic (line 124) | function panic(error)

FILE: jobsManager.js
  class JobsManager (line 4) | class JobsManager {
    method constructor (line 5) | constructor(jobWorkerEnabled, geoService, botEvent, subscriptionNotifi...
    method listen (line 29) | listen() {
    method listenEmailNotification (line 50) | listenEmailNotification(emailNotification) {
    method listenRoutingQueue (line 59) | listenRoutingQueue(routingQueue) {
    method listenScheduler (line 68) | listenScheduler(scheduler) {
    method listenActivityArchiver (line 77) | listenActivityArchiver(activityArchiver) {
    method listenWhatsappQueue (line 86) | listenWhatsappQueue(whatsappQueue) {
    method listenMultiWorker (line 96) | listenMultiWorker(multiWorkerQueue) {

FILE: middleware/file-type.js
  constant TEXT_MIME_TYPES (line 5) | const TEXT_MIME_TYPES = [
  function areMimeTypesEquivalent (line 19) | function areMimeTypesEquivalent(mimeType1, mimeType2) {
  constant MAGIC_SIGNATURES (line 52) | const MAGIC_SIGNATURES = {
  function magicMatches (line 63) | function magicMatches(buf, mimetype) {
  constant BASE64_REGEX (line 78) | const BASE64_REGEX = /^[A-Za-z0-9+/]+=*$/;
  function ensureBuffer (line 87) | function ensureBuffer(buffer) {
  function verifyFileContent (line 115) | async function verifyFileContent(buffer, mimetype) {

FILE: middleware/has-role.js
  class RoleChecker (line 6) | class RoleChecker {
    method constructor (line 10) | constructor() {
    method isType (line 24) | isType(type) {
    method isTypeAsFunction (line 39) | isTypeAsFunction(type, user) {
    method isTypesAsFunction (line 62) | isTypesAsFunction(types, user) {
    method hasPermission (line 88) | hasPermission(permission) {
    method hasRole (line 161) | hasRole(role) {
    method hasRoleOrTypes (line 165) | hasRoleOrTypes(role, types, permission) {

FILE: middleware/ipFilter.js
  class IPFilter (line 44) | class IPFilter {
    method constructor (line 48) | constructor() {
    method projectIpFilter (line 59) | projectIpFilter (req, res, next) {
    method projectIpFilterDeny (line 98) | projectIpFilterDeny (req, res, next) {
    method projectBanUserFilter (line 136) | projectBanUserFilter(req, res, next) {
    method decodeJwt (line 193) | decodeJwt(req, res, next) {

FILE: middleware/passport.js
  constant JWT_HISTORY_ENABLED (line 89) | let JWT_HISTORY_ENABLED = false;

FILE: middleware/recaptcha.js
  constant RECAPTCHA_ENABLED (line 11) | let RECAPTCHA_ENABLED =  false;

FILE: migrations/1601628781595-project_users_presence.js
  function up (line 7) | async function up () {
  function down (line 23) | async function down () {

FILE: migrations/1602847963299-message-channel_type-and-channel-fields-added--autosync.js
  function up (line 8) | async function up () {
  function down (line 21) | async function down () {

FILE: migrations/1603797978971-project_users-status-field-added--autosync.js
  function up (line 7) | async function up () {
  function down (line 23) | async function down () {

FILE: migrations/1603955232377-requests-channel-outbound-fields--autosync.js
  function up (line 8) | async function up () {
  function down (line 21) | async function down () {

FILE: migrations/1603955232378-requests-channel-fields--autosync.js
  function up (line 8) | async function up () {
  function down (line 21) | async function down () {

FILE: migrations/1604082287722-labels-data-default-fields--autosync.js
  function up (line 8) | async function up () {
  function down (line 60) | async function down () {

FILE: migrations/1604082288723-labels-waiting_time-added_suffix_reply_time--autosync.js
  function up (line 8) | async function up () {
  function down (line 66) | async function down () {

FILE: migrations/1611576399823-project-settings-max_agent_assigned_renamed--autosync.js
  function up (line 7) | async function up () {
  function down (line 25) | async function down () {

FILE: migrations/1611576534533-project_user-max_assigned_chat-renamed--autosync.js
  function up (line 7) | async function up () {
  function down (line 26) | async function down () {

FILE: migrations/1615214914082-faq-intent_id-intent_display_name-fields-added--autosync.js
  function up (line 8) | async function up () {
  function down (line 33) | async function down () {

FILE: migrations/1616685902635-request_agents_to_snapshot_agents--autosync.js
  function up (line 9) | async function up () {
  function down (line 56) | async function down () {

FILE: migrations/1616687831941-trigger_availableAgentsCount_to_snapshot_agents--autosync.js
  function up (line 10) | async function up () {
  function down (line 57) | async function down () {

FILE: migrations/1616687831941-trigger_availableAgentsCount_to_snapshot_agents-key--autosync.js
  function up (line 10) | async function up () {
  function down (line 58) | async function down () {

FILE: migrations/1619185894304-request-remove-duplicated-request-by-request_id--autosync.js
  function up (line 7) | async function up () {
  function down (line 61) | async function down () {

FILE: migrations/1752742733903-namespace-engine-migration.js
  function up (line 4) | async function up () {

FILE: migrations/1757601159298-project_user_role_type.js
  constant BATCH_SIZE (line 4) | const BATCH_SIZE = 100;
  constant PROGRESS_LOG_EVERY_BATCHES (line 6) | const PROGRESS_LOG_EVERY_BATCHES = 10;
  constant WITHOUT_ROLE_TYPE (line 9) | const WITHOUT_ROLE_TYPE = {
  constant AGENT_ROLES_FILTER (line 13) | const AGENT_ROLES_FILTER = {
  constant USER_ROLES_FILTER (line 27) | const USER_ROLES_FILTER = {
  function batchSetRoleType (line 31) | async function batchSetRoleType(filter, roleType, phaseLabel) {
  function up (line 83) | async function up() {
  function down (line 99) | async function down() {

FILE: migrations/1771844588961-phone-channels-migration.js
  constant VOICE_TWILIO_CHANNEL_NAMES (line 5) | const VOICE_TWILIO_CHANNEL_NAMES = ['voice_twilio', 'voice-twilio'];
  constant VOICE_VXML_CHANNEL_NAMES (line 6) | const VOICE_VXML_CHANNEL_NAMES = ['voice-vxml', 'voice-vxml-enghouse'];
  constant BATCH_SIZE (line 7) | const BATCH_SIZE = 100;
  function getPhoneFromVoiceTwilioCreatedBy (line 9) | function getPhoneFromVoiceTwilioCreatedBy(createdBy) {
  function updateManyWithNormalizedPhone (line 16) | async function updateManyWithNormalizedPhone(filter, getPhoneFromDoc) {
  function up (line 47) | async function up() {

FILE: models/faq_kb.js
  function generateSlug (line 264) | function generateSlug(name) {

FILE: models/kb_setting.js
  constant DEFAULT_UNANSWERED_TTL_SEC (line 5) | const DEFAULT_UNANSWERED_TTL_SEC = 7 * 24 * 60 * 60;
  constant DEFAULT_ANSWERED_TTL_SEC (line 6) | const DEFAULT_ANSWERED_TTL_SEC = 7 * 24 * 60 * 60;
  function ttlSecondsFromEnv (line 8) | function ttlSecondsFromEnv(raw, fallbackSec) {

FILE: models/permissionConstants.js
  constant OWNER_ROLE (line 1) | const OWNER_ROLE = [
  constant ADMIN_ROLE (line 4) | const ADMIN_ROLE = [
  constant AGENT_ROLE (line 7) | const AGENT_ROLE= [

FILE: models/projectf.js
  class Project (line 46) | class Project {
    method constructor (line 49) | constructor(project) {
    method save (line 87) | save(c) {
    method findOne (line 94) | static findOne(q,c) {
    method cll (line 108) | static cll(q, c) {

FILE: public/wstest/TB.js
  function createInstance (line 5) | function createInstance() {

FILE: public/wstest/tilebase.js
  class Tilebase (line 1) | class Tilebase {
    method constructor (line 4) | constructor(url, onCreate, onUpdate) {
    method send (line 33) | send (message) {
    method start (line 37) | start(initialMessage) {
    method isArray (line 131) | isArray(what) {

FILE: pubmodules/activities/activityArchiver.js
  class ActivityArchiver (line 6) | class ActivityArchiver {
    method listen (line 8) | listen() {
    method save (line 418) | save(activity) {

FILE: pubmodules/activities/routes/activity.js
  function translateString (line 306) | function translateString(string, lang) {
  function buildMsg_REQUEST_CREATE (line 416) | function buildMsg_REQUEST_CREATE(lang, activity) {
  function buildMsg_PROJECT_USER_INVITE (line 420) | function buildMsg_PROJECT_USER_INVITE(actor_name, target_fullname, lang,...
  function buildMsg_PROJECT_USER_DELETE (line 451) | function buildMsg_PROJECT_USER_DELETE(actor_name, target_fullname, lang) {
  function buildMsg_PROJECT_USER_UPDATE (line 459) | function buildMsg_PROJECT_USER_UPDATE(actor_name, target_fullname, lang,...
  function buildCsv (line 571) | function buildCsv(activities, lang) {

FILE: pubmodules/apps/listener.js
  class Listener (line 17) | class Listener {
    method listen (line 19) | listen(config) {

FILE: pubmodules/cache/mongoose-cachegoose-fn.js
  function del (line 23) | async function del(client, key, callback) {
  function listen (line 55) | function listen(client) {

FILE: pubmodules/chatbotTemplates/listener.js
  class Listener (line 5) | class Listener {
    method listen (line 7) | listen(config) {

FILE: pubmodules/dialogflow/listener.js
  constant BOT_DIALOGFLOW_ENDPOINT (line 9) | const BOT_DIALOGFLOW_ENDPOINT = process.env.BOT_DIALOGFLOW_ENDPOINT || "...
  class Listener (line 22) | class Listener {
    method listen (line 24) | listen(config) {

FILE: pubmodules/emailNotification/requestNotification.js
  function getPooledEmailCache (line 46) | function getPooledEmailCache() {
  constant POOLED_EMAIL_THROTTLE_TTL (line 59) | const POOLED_EMAIL_THROTTLE_TTL = Number(process.env.POOLED_EMAIL_THROTT...
  class RequestNotification (line 61) | class RequestNotification {
    method constructor (line 63) | constructor() {
    method setTdCache (line 75) | setTdCache(tdCache) {
    method listen (line 82) | listen() {
    method sendToUserEmailChannelEmail (line 297) | sendToUserEmailChannelEmail(projectid, message) {
    method notifyFollowers (line 415) | async notifyFollowers(savedRequest, project, message) {
    method sendToFollower (line 480) | sendToFollower(projectid, message) {
    method sendToAgentEmailChannelEmail (line 519) | sendToAgentEmailChannelEmail(projectid, message) {
    method sendEmailChannelTakingNotification (line 691) | sendEmailChannelTakingNotification(projectid, request) {
    method sendUserEmail (line 730) | sendUserEmail(projectid, message) {
    method sendAgentEmail (line 885) | sendAgentEmail(projectid, savedRequest) {
    method sendTranscriptByEmail (line 1102) | sendTranscriptByEmail(sendTo, request_id, id_project, project) {

FILE: pubmodules/entityEraser/eraserInterceptor.js
  class EntityInterceptor (line 9) | class EntityInterceptor {
    method listen (line 14) | listen() {

FILE: pubmodules/events/event2Event.js
  class Event2Event (line 7) | class Event2Event extends EventEmitter2 {}

FILE: pubmodules/events/eventEvent.js
  class EventEvent (line 3) | class EventEvent extends EventEmitter {}

FILE: pubmodules/events/eventService.js
  class EventService (line 9) | class EventService {
    method emit (line 43) | emit(name, attributes, id_project, project_user, createdBy, status, us...

FILE: pubmodules/kaleyra/listener.js
  class Listener (line 8) | class Listener {
    method listen (line 10) | listen(config) {

FILE: pubmodules/messageActions/event/messageActionEvent.js
  class MessageActionEvent (line 3) | class MessageActionEvent extends EventEmitter {}

FILE: pubmodules/messageActions/messageActionsInterceptor.js
  class MessageActionsInterceptor (line 12) | class MessageActionsInterceptor {
    method listen (line 17) | listen() {

FILE: pubmodules/messageTransformer/messageHandlebarsTransformerInterceptor.js
  class MessageHandlebarsTransformerInterceptor (line 9) | class MessageHandlebarsTransformerInterceptor {
    method listen (line 14) | listen() {

FILE: pubmodules/messageTransformer/messageTransformerInterceptor.js
  class MessageTransformerInterceptor (line 11) | class MessageTransformerInterceptor {
    method listen (line 16) | listen() {

FILE: pubmodules/messageTransformer/microLanguageAttributesTransformerInterceptor.js
  class MicroLanguageTransformerInterceptor (line 8) | class MicroLanguageTransformerInterceptor {
    method listen (line 13) | listen() {

FILE: pubmodules/messageTransformer/microLanguageTransformerInterceptor.js
  class MicroLanguageTransformerInterceptor (line 8) | class MicroLanguageTransformerInterceptor {
    method listen (line 13) | listen() {

FILE: pubmodules/messenger/listener.js
  class Listener (line 11) | class Listener {
    method listen (line 13) | listen(config) {

FILE: pubmodules/mqttTest/listener.js
  class Listener (line 8) | class Listener {
    method listen (line 10) | listen(config) {

FILE: pubmodules/pubModulesManager.js
  class PubModulesManager (line 8) | class PubModulesManager {
    method constructor (line 10) | constructor() {
    method use (line 81) | use(app) {
    method useUnderProjects (line 138) | useUnderProjects(app) {
    method init (line 174) | init(config) {
    method start (line 600) | start() {

FILE: pubmodules/queue/reconnect.js
  function start (line 38) | function start() {
  function whenConnected (line 61) | function whenConnected() {
  function startPublisher (line 82) | function startPublisher() {
  function publish (line 102) | function publish(exchange, routingKey, content) {
  function startWorker (line 122) | function startWorker() {
  function work (line 238) | function work(msg, cb) {
  function closeOnErr (line 347) | function closeOnErr(err) {
  function listen (line 361) | function listen() {

FILE: pubmodules/queue/reconnectFanout.js
  function start (line 20) | function start() {
  function whenConnected (line 43) | function whenConnected() {
  function startPublisher (line 50) | function startPublisher() {
  function publish (line 70) | function publish(exchange, routingKey, content) {
  function startWorker (line 92) | function startWorker() {
  function work (line 158) | function work(msg, cb) {
  function closeOnErr (line 197) | function closeOnErr(err) {
  function listen (line 216) | function listen() {

FILE: pubmodules/rasa/listener.js
  constant BOT_RASA_ENDPOINT (line 9) | const BOT_RASA_ENDPOINT = process.env.BOT_RASA_ENDPOINT || "http://local...
  class Listener (line 21) | class Listener {
    method listen (line 23) | listen(config) {

FILE: pubmodules/routing-queue/listener.js
  class Listener (line 22) | class Listener {
    method constructor (line 25) | constructor() {
    method nextOperator (line 33) | nextOperator(array, index) {
    method listen (line 53) | listen() {

FILE: pubmodules/routing-queue/listenerQueued.js
  class Listener (line 29) | class Listener {
    method constructor (line 32) | constructor() {
    method updateProjectUser (line 45) | updateProjectUser(id_user, id_project, operation) {
    method _updateProjectUser (line 93) | _updateProjectUser(id_user, id_project, operation) {
    method updateParticipatingProjectUsers (line 126) | updateParticipatingProjectUsers(request, operation) {
    method listen (line 139) | listen() {

FILE: pubmodules/rules/appRules.js
  class AppRules (line 5) | class AppRules {
    method start (line 7) | start() {

FILE: pubmodules/rules/conciergeBot.js
  class ConciergeBot (line 14) | class ConciergeBot {
    method listen (line 17) | listen() {
    method welcomeAgentOnJoin (line 320) | welcomeAgentOnJoin(request, member) {

FILE: pubmodules/s/stripe/index.js
  function getSubByIdAndCheckoutSessionCompletedEvnt (line 1) | function getSubByIdAndCheckoutSessionCompletedEvnt(_0x430853){return new...
  function getSubscritionById (line 1) | function getSubscritionById(_0xb497f4){return new Promise(function(_0x4d...
  function updateProjectProfile (line 1) | function updateProjectProfile(_0x26b5d9,_0x36c121,_0xf15e2d){Project[_0x...
  function saveOnDB (line 1) | function saveOnDB(_0x462571,_0x47f4eb,_0x696fb9,_0x51275d,_0x27b4c5,_0x4...
  function saveFirstInvoicePaymentSucceeded (line 1) | function saveFirstInvoicePaymentSucceeded(_0x19242c,_0x27ead3,_0x37ca2c)...
  function detachPaymentFunc (line 1) | async function detachPaymentFunc(_0x5c72a1,_0x5379eb){winston[_0x15b1('0...

FILE: pubmodules/scheduler/taskRunner.js
  class TaskRunner (line 9) | class TaskRunner {
    method constructor (line 11) | constructor() {
    method start (line 15) | start() {

FILE: pubmodules/scheduler/tasks/closeAgentUnresponsiveRequestTask.js
  class CloseAgentUnresponsiveRequestTask (line 13) | class CloseAgentUnresponsiveRequestTask {
    method constructor (line 15) | constructor() {
    method run (line 33) | run() {
    method scheduleUnresponsiveRequests (line 45) | scheduleUnresponsiveRequests() {
    method findUnresponsiveRequests (line 63) | findUnresponsiveRequests() {
    method scheduleRequestClosures (line 113) | scheduleRequestClosures(requests) {
    method closeRequest (line 129) | closeRequest(request) {

FILE: pubmodules/scheduler/tasks/closeBotUnresponsiveRequestTask.js
  class CloseBotUnresponsiveRequestTask (line 13) | class CloseBotUnresponsiveRequestTask {
    method constructor (line 15) | constructor() {
    method run (line 32) | run() {
    method scheduleUnresponsiveRequests (line 45) | scheduleUnresponsiveRequests() {
    method findUnresponsiveRequests (line 71) | findUnresponsiveRequests() {
    method scheduleRequestClosures (line 120) | scheduleRequestClosures(requests) {
    method closeRequest (line 136) | closeRequest(request) {

FILE: pubmodules/scheduler/tasks/requestTaskSchedulerAgenda.js
  class RequestTaskScheduler (line 17) | class RequestTaskScheduler {
    method constructor (line 19) | constructor() {
    method run (line 24) | run() {
    method scheduleUnresponsiveRequests (line 31) | scheduleUnresponsiveRequests() {
    method findUnresponsiveRequests (line 56) | findUnresponsiveRequests() {

FILE: pubmodules/sms/listener.js
  class Listener (line 11) | class Listener {
    method listen (line 13) | listen(config) {

FILE: pubmodules/telegram/listener.js
  class Listener (line 11) | class Listener {
    method listen (line 13) | listen(config) {

FILE: pubmodules/tilebot/listener.js
  constant TILEBOT_ENDPOINT (line 8) | let TILEBOT_ENDPOINT = "http://localhost:" + port + "/modules/tilebot/ex...
  class Listener (line 25) | class Listener {
    method listen (line 27) | listen(config) {

FILE: pubmodules/trigger/event/actionEventEmitter.js
  class ActionEventEmitter (line 3) | class ActionEventEmitter extends EventEmitter {}

FILE: pubmodules/trigger/event/flowEventEmitter.js
  class FlowEventEmitter (line 3) | class FlowEventEmitter extends EventEmitter {}

FILE: pubmodules/trigger/event/triggerEventEmitter.js
  class TriggerEventEmitter (line 3) | class TriggerEventEmitter extends EventEmitter {}

FILE: pubmodules/trigger/rulesTrigger.js
  constant TILEBOT_ENDPOINT (line 36) | let TILEBOT_ENDPOINT = "http://localhost:" + port + "/modules/tilebot/ex...
  class RulesTrigger (line 42) | class RulesTrigger {
    method constructor (line 44) | constructor() {
    method listen (line 54) | listen(success, error) {
    method runAction (line 129) | runAction() {
    method delayedFunction (line 1198) | delayedFunction(action,triggerEvent, waitTime) {
    method createEngine (line 1206) | createEngine() {
    method exec (line 1210) | exec(event, eventKey,successCall,errorCall) {

FILE: pubmodules/trigger/start.js
  function saveTrigger (line 10) | function saveTrigger(trigger) {

FILE: pubmodules/voice-twilio/listener.js
  class Listener (line 11) | class Listener {
    method listen (line 13) | async listen(config) {

FILE: pubmodules/voice/listener.js
  class Listener (line 11) | class Listener {
    method listen (line 13) | listen(config) {

FILE: pubmodules/whatsapp/listener.js
  class Listener (line 11) | class Listener {
    method listen (line 13) | listen(config) {

FILE: routes/answered.js
  function validateNamespace (line 214) | async function validateNamespace(id_project, namespace_id) {

FILE: routes/campaigns.js
  function sleep (line 256) | function sleep(ms) {

FILE: routes/faq.js
  constant MAX_UPLOAD_FILE_SIZE (line 24) | let MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;
  function myrequest (line 681) | async function myrequest(options, callback) {

FILE: routes/faq_kb.js
  constant MAX_UPLOAD_FILE_SIZE (line 26) | let MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;

FILE: routes/files.js
  constant MAX_UPLOAD_FILE_SIZE (line 23) | let MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;

FILE: routes/filesp.js
  constant MAX_UPLOAD_FILE_SIZE (line 25) | let MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE || 1024000;
  function getMimeTypes (line 99) | function getMimeTypes(allowed_extension) {
  function areMimeTypesEquivalent (line 112) | function areMimeTypesEquivalent(mimeType1, mimeType2) {

FILE: routes/images.js
  constant MAX_UPLOAD_FILE_SIZE (line 39) | let MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;

FILE: routes/kb.js
  constant AMQP_MANAGER_URL (line 25) | const AMQP_MANAGER_URL = process.env.AMQP_MANAGER_URL;
  constant JOB_TOPIC_EXCHANGE (line 26) | const JOB_TOPIC_EXCHANGE = process.env.JOB_TOPIC_EXCHANGE_TRAIN || 'tile...
  constant JOB_TOPIC_EXCHANGE_HYBRID (line 27) | const JOB_TOPIC_EXCHANGE_HYBRID = process.env.JOB_TOPIC_EXCHANGE_TRAIN_H...
  constant KB_WEBHOOK_TOKEN (line 28) | const KB_WEBHOOK_TOKEN = process.env.KB_WEBHOOK_TOKEN || 'kbcustomtoken';
  constant PINECONE_RERANKING (line 29) | const PINECONE_RERANKING = process.env.PINECONE_RERANKING === true || pr...
  constant MAX_UPLOAD_FILE_SIZE (line 33) | let MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;
  constant RAG_CONTEXT_ENV_OVERRIDES (line 90) | const RAG_CONTEXT_ENV_OVERRIDES = {
  function getRagContextTemplate (line 106) | function getRagContextTemplate(modelName) {
  function normalizeEmbedding (line 117) | function normalizeEmbedding(embedding) {
  function normalizeSituatedContext (line 124) | function normalizeSituatedContext() {
  function extractContent (line 475) | function extractContent(obj) {
  function normalizeKbQaPayload (line 486) | function normalizeKbQaPayload(obj) {
  function isMetadataPayload (line 494) | function isMetadataPayload(obj, streamedContent) {
  function isKbStreamCompletedSummary (line 506) | function isKbStreamCompletedSummary(obj) {
  function forwardSsePayload (line 513) | function forwardSsePayload(payload) {

FILE: routes/llm.js
  constant MAX_UPLOAD_FILE_SIZE (line 10) | let MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;

FILE: routes/request.js
  constant AMQP_MANAGER_URL (line 42) | const AMQP_MANAGER_URL = process.env.AMQP_MANAGER_URL;
  function scheduleTags (line 2451) | async function scheduleTags(id_project, tags) {

FILE: routes/unanswered.js
  function validateNamespace (line 256) | async function validateNamespace(id_project, namespace_id) {

FILE: routes/webhook.js
  constant TILEBOT_ENDPOINT (line 15) | let TILEBOT_ENDPOINT = "http://localhost:" + port + "/modules/tilebot/";
  constant KB_WEBHOOK_TOKEN (line 21) | const KB_WEBHOOK_TOKEN = process.env.KB_WEBHOOK_TOKEN || 'kbcustomtoken';
  constant AMQP_MANAGER_URL (line 22) | const AMQP_MANAGER_URL = process.env.AMQP_MANAGER_URL;
  constant JOB_TOPIC_EXCHANGE (line 23) | const JOB_TOPIC_EXCHANGE = process.env.JOB_TOPIC_EXCHANGE_TRAIN || 'tile...

FILE: services/BotSubscriptionNotifier.js
  class BotSubscriptionNotifier (line 20) | class BotSubscriptionNotifier {
    method notify (line 23) | notify(bot,secret, payload) {
    method start (line 94) | start() {

FILE: services/QuoteManager.js
  constant PLANS_LIST (line 17) | const PLANS_LIST = {
  class QuoteManager (line 38) | class QuoteManager {
    method constructor (line 40) | constructor(config) {
    method incrementRequestsCount (line 56) | async incrementRequestsCount(project, request) {
    method incrementMessagesCount (line 67) | async incrementMessagesCount(project, message) {
    method incrementEmailCount (line 77) | async incrementEmailCount(project, email) {
    method incrementTokenCount (line 88) | async incrementTokenCount(project, data) { // ?? cosa passo? il messag...
    method incrementVoiceDurationCount (line 106) | async incrementVoiceDurationCount(project, request) {
    method generateKey (line 127) | async generateKey(object, type) {
    method getCurrentQuote (line 209) | async getCurrentQuote(project, object, type) {
    method getAllQuotes (line 221) | async getAllQuotes(project, obj) {
    method checkQuote (line 243) | async checkQuote(project, object, type) {
    method checkQuoteForAlert (line 269) | async checkQuoteForAlert(project, object, type) {
    method sendEmailIfQuotaExceeded (line 291) | async sendEmailIfQuotaExceeded(project, object, type, key) {
    method percentageCalculator (line 328) | async percentageCalculator(limit, quote) {
    method invalidateCheckpointKeys (line 341) | async invalidateCheckpointKeys(project, obj) {
    method generateQuotesObject (line 369) | async generateQuotesObject(quotes, limits) {
    method getPlanLimits (line 388) | async getPlanLimits(project) {
    method getCurrentSlot (line 461) | async getCurrentSlot(project) {
    method start (line 508) | start() {

FILE: services/RateManager.js
  class RateManager (line 4) | class RateManager {
    method constructor (line 6) | constructor(config) {
    method canExecute (line 36) | async canExecute(projectOrId, id, type) {
    method getRateConfig (line 64) | async getRateConfig(project, type) {
    method setBucket (line 82) | async setBucket(key, bucket) {
    method getBucket (line 87) | async getBucket(key, type, project) {
    method createBucket (line 96) | async createBucket(type, project) {
    method loadProject (line 104) | async loadProject(id_project) {

FILE: services/Scheduler.js
  class Scheduler (line 6) | class Scheduler {
    method constructor (line 8) | constructor(config) {
    method trainSchedule (line 21) | trainSchedule(data, callback) {
    method tagSchedule (line 32) | tagSchedule(data, callback) {

FILE: services/aiManager.js
  constant KB_WEBHOOK_TOKEN (line 13) | const KB_WEBHOOK_TOKEN = process.env.KB_WEBHOOK_TOKEN || 'kbcustomtoken';
  constant AMQP_MANAGER_URL (line 14) | const AMQP_MANAGER_URL = process.env.AMQP_MANAGER_URL || 'amqp://localho...
  constant JOB_TOPIC_EXCHANGE (line 15) | const JOB_TOPIC_EXCHANGE = process.env.JOB_TOPIC_EXCHANGE_TRAIN || 'tile...
  constant JOB_TOPIC_EXCHANGE_HYBRID (line 16) | const JOB_TOPIC_EXCHANGE_HYBRID = process.env.JOB_TOPIC_EXCHANGE_TRAIN_H...
  class AiManager (line 55) | class AiManager {
    method constructor (line 57) | constructor() { }
    method addMultipleUrls (line 59) | async addMultipleUrls(namespace, urls, options) {
    method scheduleSitemap (line 132) | async scheduleSitemap(namespace, sitemap_content, options) {
    method updateSitemap (line 161) | async updateSitemap(id_project, namespace, old_sitemap, new_sitemap) {
    method shouldStop (line 215) | shouldStop(oldObj, newObj, fieldsToCheck) {
    method checkNamespace (line 228) | async checkNamespace(id_project, namespace_id) {
    method resolveLLMConfig (line 248) | async resolveLLMConfig(id_project, provider = 'openai', model) {
    method checkQuotaAvailability (line 285) | async checkQuotaAvailability(quoteManager, project, ncontents) {
    method fetchSitemap (line 309) | async fetchSitemap(sitemapUrl) {
    method foundSitemapChanges (line 331) | async foundSitemapChanges(existingKbs, urls) {
    method generateFilename (line 348) | async generateFilename(name) {
    method getKeyFromIntegrations (line 360) | async getKeyFromIntegrations(project_id) {
    method removeMultipleContents (line 376) | async removeMultipleContents(namespace, kbs) {
    method saveBulk (line 410) | async saveBulk(operations, kbs, project_id, namespace) {
    method setDefaultScrapeOptions (line 430) | setDefaultScrapeOptions() {
    method scheduleScrape (line 438) | async scheduleScrape(resources, hybrid) {
    method startScrape (line 488) | async startScrape(data) {
    method statusConverter (line 517) | async statusConverter(status) {
    method updateStatus (line 541) | async updateStatus(id, status, error) {
    method normalizeSituatedContext (line 570) | normalizeSituatedContext() {

FILE: services/aiReindexService.js
  class AiReindexService (line 5) | class AiReindexService {
    method constructor (line 7) | constructor() {
    method delete (line 29) | async delete(content_id) {
    method findScheduler (line 65) | async findScheduler(id) {
    method offlineScheduler (line 90) | async offlineScheduler(id) {
    method offlineWorkflow (line 107) | async offlineWorkflow(code) {
    method deleteWorkflow (line 129) | async deleteWorkflow(code) {

FILE: services/aiService.js
  class AiService (line 14) | class AiService {
    method completions (line 17) | completions(data, gptkey) {
    method transcription (line 41) | transcription(buffer, gptkey) {
    method askllm (line 69) | askllm(data) {
    method singleScrape (line 155) | singleScrape(data) {
    method scrapeStatus (line 180) | scrapeStatus(data) {
    method askNamespace (line 201) | askNamespace(data) {
    method askStream (line 233) | askStream(data) {
    method getContentChunks (line 252) | getContentChunks(namespace_id, content_id, engine, hybrid) {
    method deleteIndex (line 274) | deleteIndex(data) {
    method deleteNamespace (line 295) | deleteNamespace(data) {

FILE: services/banUserNotifier.js
  class BanUserNotifier (line 8) | class BanUserNotifier {
    method listen (line 10) | listen() {

FILE: services/bootDataLoader.js
  class BootDataLoader (line 9) | class BootDataLoader {
    method create (line 12) | create() {

FILE: services/cacheEnabler.js
  class CacheEnabler (line 2) | class CacheEnabler {
    method constructor (line 3) | constructor() {

FILE: services/chatbotService.js
  class ChatbotService (line 5) | class ChatbotService {
    method constructor (line 7) | constructor() {}
    method fork (line 9) | async fork(id_faq_kb, api_url, token, project_id) {
    method getBotById (line 30) | async getBotById(id_faq_kb, published, api_url, chatbot_templates_api_...
    method createBot (line 74) | async createBot(api_url, token, chatbot, project_id) {
    method importFaqs (line 96) | async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {
    method setModified (line 117) | async setModified(chatbot_id, modified) {

FILE: services/departmentService.js
  class DepartmentService (line 14) | class DepartmentService {
    method createDefault (line 16) | createDefault(project_id, createdBy) {
    method create (line 20) | create(name, id_project, routing, createdBy, isdefault) {
    method nextOperator (line 48) | nextOperator (array, index) {
    method roundRobin (line 68) | roundRobin(operatorSelectedEvent) {
    method getOperators (line 186) | getOperators(departmentid, projectid, nobot, disableWebHookCall, conte...
    method findProjectUsersAllAndAvailableWithOperatingHours (line 355) | findProjectUsersAllAndAvailableWithOperatingHours(projectid, departmen...
    method findProjectUsersAllAndAvailableWithOperatingHours_group (line 374) | findProjectUsersAllAndAvailableWithOperatingHours_group(projectid, dep...
    method findProjectUsersAllAndAvailableWithOperatingHours_nogroup (line 449) | findProjectUsersAllAndAvailableWithOperatingHours_nogroup(projectid, d...
    method getAvailableOperatorsWithOperatingHours (line 527) | getAvailableOperatorsWithOperatingHours(project_users, projectid) {
    method getAvailableOperator (line 566) | getAvailableOperator(project_users) {
    method getDefaultDepartment (line 579) | getDefaultDepartment(projectid) {
    method getRandomAvailableOperator (line 604) | getRandomAvailableOperator(project_users_available) {
    method isGroupInProjectDepartment (line 639) | async isGroupInProjectDepartment(projectId, groupId) {

FILE: services/emailService.js
  constant MESSAGE_ID_DOMAIN (line 55) | const MESSAGE_ID_DOMAIN = "tiledesk.com";
  class EmailService (line 59) | class EmailService {
    method constructor (line 61) | constructor() {
    method readTemplate (line 152) | readTemplate(templateName, settings, environmentVariableKey) {
    method readTemplateFile (line 209) | readTemplateFile(templateName) {
    method getTransport (line 227) | getTransport(configEmail) {
    method send (line 284) | async send(mail, quoteEnabled, project, quoteManager) {
    method sendTest (line 365) | async sendTest(to, configEmail, callback) {
    method sendNewAssignedRequestNotification (line 385) | async sendNewAssignedRequestNotification(to, request, project) {
    method sendNewAssignedAgentMessageEmailNotification (line 538) | async sendNewAssignedAgentMessageEmailNotification(to, request, projec...
    method sendNewPooledRequestNotification (line 682) | async sendNewPooledRequestNotification(to, request, project) {
    method sendNewPooledMessageEmailNotification (line 811) | async sendNewPooledMessageEmailNotification(to, request, project, mess...
    method sendNewMessageNotification (line 956) | async sendNewMessageNotification(to, message, project, tokenQueryStrin...
    method sendEmailChannelNotification (line 1081) | async sendEmailChannelNotification(to, message, project, tokenQueryStr...
    method sendFollowerNotification (line 1258) | async sendFollowerNotification(to, message, project) {
    method sendEmailDirect (line 1465) | async sendEmailDirect(to, text, project, request_id, subject, tokenQue...
    method sendPasswordResetRequestEmail (line 1558) | async sendPasswordResetRequestEmail(to, resetPswRequestId, userFirstna...
    method sendYourPswHasBeenChangedEmail (line 1588) | async sendYourPswHasBeenChangedEmail(to, userFirstname, userLastname) {
    method sendYouHaveBeenInvited (line 1624) | async sendYouHaveBeenInvited(to, currentUserFirstname, currentUserLast...
    method sendInvitationEmail_UserNotRegistered (line 1662) | async sendInvitationEmail_UserNotRegistered(to, currentUserFirstname, ...
    method sendVerifyEmailAddress (line 1695) | async sendVerifyEmailAddress(to, savedUser, code) {
    method sendRequestTranscript (line 1735) | async sendRequestTranscript(to, messages, request, project) {
    method sendEmailRedirectOnDesktop (line 1814) | async sendEmailRedirectOnDesktop(to, token, project_id, chatbot_id, na...
    method sendEmailQuotaCheckpointReached (line 1850) | async sendEmailQuotaCheckpointReached(to, firstname, project_name, res...
    method parseText (line 1888) | parseText(text, payload) {
    method formatText (line 1913) | formatText(templateName, defaultText, payload, settings) {
    method getTemplate (line 1946) | getTemplate(templateName, settings) {

FILE: services/faqBotHandler.js
  class FaqBotHandler (line 19) | class FaqBotHandler {
    method is_command (line 21) | static is_command(text) {
    method listen (line 72) | listen() {

FILE: services/faqBotSupport.js
  class FaqBotSupport (line 22) | class FaqBotSupport {
    method getMessage (line 27) | getMessage(key, lang, labelsObject) {
    method parseMicrolanguage (line 54) | parseMicrolanguage(text, message, bot, faq, disableWebHook, json) {
    method getParsedMessage (line 222) | getParsedMessage(text, message, bot, faq) {
    method getBotMessage (line 338) | getBotMessage(botAnswer, projectid, bot, message, threshold) {

FILE: services/faqService.js
  class FaqService (line 9) | class FaqService {
    method create (line 11) | create(id_project, id_user, data) {
    method #resolveTemplate (line 70) | #resolveTemplate(subtype, template) {
    method createGreetingsAndOperationalsFaqs (line 79) | createGreetingsAndOperationalsFaqs(faq_kb_id, created_by, projectid, t...
    method getAll (line 118) | getAll(faq_kb_id) {

FILE: services/fileGridFsService.js
  class FileGridFsService (line 16) | class FileGridFsService extends FileService {
    method constructor (line 18) | constructor(bucketName) {
    method createRetentionIndex (line 72) | createRetentionIndex(name) {
    method createFile (line 91) | async createFile ( filename, data, path, contentType, options) {
    method find (line 114) | async find(filename) {
    method deleteFile (line 125) | async deleteFile(filename) {
    method getFileDataAsStream (line 144) | getFileDataAsStream (filename) {
    method getFileDataAsBuffer (line 157) | getFileDataAsBuffer (filename) {
    method getStorage (line 189) | getStorage(folderName) {
    method getStorageFixFolder (line 254) | getStorageFixFolder(folderName) {
    method getStorageAvatar (line 333) | getStorageAvatar(folderName) {
    method getStorageAvatarFiles (line 445) | getStorageAvatarFiles(folderName) {
    method getStorageProjectAssets (line 517) | getStorageProjectAssets(folderName) {

FILE: services/fileService.js
  class FileService (line 1) | class FileService {
    method constructor (line 3) | constructor() {}
    method createFile (line 5) | createFile ( fileName, data, contentType, options) {}
    method getFileData (line 6) | getFileData (filename) {}
    method deleteFile (line 7) | deleteFile(fileName) {}

FILE: services/filesystemService.js
  class FilesystemService (line 2) | class FilesystemService extends FileService {

FILE: services/geoService.js
  class GeoService (line 11) | class GeoService {
    method constructor (line 13) | constructor() {
    method listen (line 26) | listen() {

FILE: services/integrationService.js
  class IntegrationService (line 4) | class IntegrationService {
    method getIntegration (line 8) | async getIntegration(id_project, integration_name) {
    method getKeyFromIntegration (line 27) | async getKeyFromIntegration(id_project, integration_name = 'openai') {

FILE: services/labelService-no-default.js
  class LabelService (line 9) | class LabelService {
    method fetchPivotDefault (line 12) | fetchPivotDefault() {
    method fetchDefault (line 25) | fetchDefault() {
    method get (line 44) | async get(id_project, language, key) {
    method getByLanguageAndKey (line 56) | getByLanguageAndKey(id_project, language, key) {
    method getAllByLanguage (line 75) | async getAllByLanguage(id_project, language) {
    method getAllByLanguageNoPivot (line 94) | getAllByLanguageNoPivot(id_project, language) {
    method getAllMerged (line 112) | getAllMerged(id_project) {
    method getAll (line 157) | getAll(id_project) {

FILE: services/labelService.js
  class LabelService (line 9) | class LabelService {
    method constructor (line 11) | constructor() {
    method get (line 18) | async get(id_project, language, key) {
    method getLanguage (line 26) | async getLanguage(id_project, language) {
    method getAll (line 64) | getAll(id_project) {
    method fetchPivotDefault (line 101) | fetchPivotDefault() {
    method fetchDefault (line 114) | fetchDefault() {

FILE: services/leadService.js
  class LeadService (line 12) | class LeadService {
    method findByEmail (line 15) | findByEmail(email, id_project) {
    method createIfNotExists (line 36) | createIfNotExists(fullname, email, id_project, createdBy, attributes, ...
    method create (line 53) | create(fullname, email, id_project, createdBy, attributes, status) {
    method createIfNotExistsWithLeadId (line 59) | createIfNotExistsWithLeadId(lead_id, fullname, email, id_project, crea...
    method updateStatusWitId (line 90) | updateStatusWitId(lead_id, id_project, status) {
    method updateWitId (line 115) | updateWitId(lead_id, fullname, email, id_project, status, phone) {
    method createWitId (line 153) | createWitId(lead_id, fullname, email, id_project, createdBy, attribute...

FILE: services/logsService.js
  class LogsService (line 8) | class LogsService {
    method getLastRows (line 10) | async getLastRows(id, limit, logLevel, queryField = 'request_id') {
    method getOlderRows (line 31) | async getOlderRows(id, limit, logLevel, timestamp, queryField = 'reque...
    method getNewerRows (line 52) | async getNewerRows(id, limit, logLevel, timestamp, queryField = 'reque...

FILE: services/mcpService.js
  class MCPService (line 11) | class MCPService {
    method constructor (line 13) | constructor() {
    method parseSSEResponse (line 25) | parseSSEResponse(sseData) {
    method sendJSONRPCRequest (line 48) | async sendJSONRPCRequest(url, request, auth, sessionId) {
    method getCacheKey (line 133) | getCacheKey(url, projectId) {
    method initializeServer (line 142) | async initializeServer(serverConfig) {
    method listTools (line 209) | async listTools(serverConfig) {
    method removeConnection (line 266) | removeConnection(url, projectId) {
    method clearConnections (line 274) | clearConnections() {

FILE: services/messageService.js
  class MessageService (line 15) | class MessageService {
    method send (line 17) | send(sender, senderFullname, recipient, text, id_project, createdBy, a...
    method upsert (line 21) | upsert(id, sender, senderFullname, recipient, text, id_project, create...
    method create (line 30) | create(sender, senderFullname, recipient, text, id_project, createdBy,...
    method save (line 49) | save(message) {
    method emitMessage (line 203) | emitMessage(message) {
    method changeStatus (line 224) | changeStatus(message_id, newstatus) {
    method getTranscriptByRequestId (line 246) | getTranscriptByRequestId(requestid, id_project) {
    method getAudioTranscription (line 297) | getAudioTranscription(id_project, audio_url) {

FILE: services/modulesManager.js
  class ModulesManager (line 30) | class ModulesManager {
    method constructor (line 32) | constructor() {
    method injectBefore (line 47) | injectBefore(app) {
    method injectAfter (line 68) | injectAfter(httpServer,app,port) {
    method use (line 78) | use(app) {
    method useUnderProjects (line 95) | useUnderProjects(app) {
    method init (line 127) | init(config) {
    method start (line 267) | start() {

FILE: services/mongoose-cache.js
  class MongooseCache (line 4) | class MongooseCache {
    method constructor (line 6) | constructor(mongoose, option) {

FILE: services/operatingHoursService.js
  class OperatingHoursService (line 9) | class OperatingHoursService {
    method projectIsOpenNow (line 11) | projectIsOpenNow(projectId, callback) {
    method slotIsOpenNow (line 178) | slotIsOpenNow(projectId, slot_id, callback) {
  function slotCheck (line 261) | function slotCheck(currentTime, tzname, slot) {
  function addOrSubstractProjcTzOffsetFromDateNow (line 275) | function addOrSubstractProjcTzOffsetFromDateNow(prjcTimezoneName) {
  function checkDay (line 342) | function checkDay(operatingHoursPars, dayNowAtPrjctTz) {
  function checkTimes (line 357) | function checkTimes(operatingHoursPars, dayNowAtPrjctTz, timeNowAtPrjctT...

FILE: services/pendingInvitationService.js
  class Pending_Invitation (line 9) | class Pending_Invitation {
    method saveInPendingInvitation (line 12) | saveInPendingInvitation(project_id, project_name, invited_user_email, ...
    method checkNewUserInPendingInvitationAndSavePrcjUser (line 68) | checkNewUserInPendingInvitationAndSavePrcjUser(newUserEmail, newUserId) {
    method removePendingInvitation (line 123) | removePendingInvitation(pendingInvitationId) {

FILE: services/projectService.js
  class ProjectService (line 13) | class ProjectService {
    method createAndReturnProjectAndProjectUser (line 15) | createAndReturnProjectAndProjectUser(name, createdBy, settings) {
    method create (line 67) | create(name, createdBy, settings) {
    method getCachedProject (line 78) | async getCachedProject(id_project) {

FILE: services/projectUserService.js
  class ProjectUserService (line 10) | class ProjectUserService {
    method checkAgentsAvailablesWithSmartAssignment (line 12) | checkAgentsAvailablesWithSmartAssignment(project, available_agents) {
    method getWithPermissions (line 45) | async getWithPermissions(id_user, id_project, user_sub) {
    method getPermissions (line 65) | async getPermissions(role, id_project) {
    method get (line 86) | async get(id_user, id_project, user_sub) {

FILE: services/requestService.js
  constant TILEBOT_ENDPOINT (line 24) | let TILEBOT_ENDPOINT = "http://localhost:" + port + "/modules/tilebot/ex...
  class RequestService (line 37) | class RequestService {
    method constructor (line 39) | constructor() {
    method listen (line 43) | listen() {
    method getAvailableAgentsCount (line 144) | getAvailableAgentsCount(agents) {
    method routeInternal (line 163) | routeInternal(request, departmentid, id_project, nobot) {
    method route (line 248) | async route(request_id, departmentid, id_project, nobot, no_populate) {
    method reroute (line 397) | reroute(request_id, id_project, nobot) {
    method createWithRequester (line 446) | createWithRequester(project_user_id, lead_id, id_project, first_text, ...
    method createWithIdAndRequester (line 454) | createWithIdAndRequester(request_id, project_user_id, lead_id, id_proj...
    method create (line 465) | async create(request) {
    method createWithId (line 935) | createWithId(request_id, requester_id, id_project, first_text, departm...
    method changeStatusByRequestId (line 1060) | changeStatusByRequestId(request_id, id_project, newstatus) {
    method changeFirstTextAndPreflightByRequestId (line 1092) | changeFirstTextAndPreflightByRequestId(request_id, id_project, first_t...
    method changeFirstTextByRequestId (line 1133) | changeFirstTextByRequestId(request_id, id_project, first_text) {
    method changePreflightByRequestId (line 1166) | changePreflightByRequestId(request_id, id_project, preflight) {
    method setClosedAtByRequestId (line 1195) | setClosedAtByRequestId(request_id, id_project, created_at, closed_at, ...
    method incrementMessagesCountByRequestId (line 1238) | incrementMessagesCountByRequestId(request_id, id_project) {
    method updateWaitingTimeByRequestId (line 1257) | updateWaitingTimeByRequestId(request_id, id_project, enable_populate) {
    method closeRequestByRequestId (line 1324) | closeRequestByRequestId(request_id, id_project, skipStatsUpdate, notif...
    method reopenRequestByRequestId (line 1417) | reopenRequestByRequestId(request_id, id_project) {
    method updateTrascriptByRequestId (line 1493) | updateTrascriptByRequestId(request_id, id_project, transcript) {
    method findByRequestId (line 1512) | findByRequestId(request_id, id_project) {
    method setParticipantsByRequestId (line 1526) | setParticipantsByRequestId(request_id, id_project, newparticipants) {
    method addParticipantByRequestId (line 1686) | addParticipantByRequestId(request_id, id_project, member) {
    method removeParticipantByRequestId (line 1795) | removeParticipantByRequestId(request_id, id_project, member) {
    method updateAttributesByRequestId (line 1950) | updateAttributesByRequestId(request_id, id_project, attributes) {
    method addTagByRequestId (line 2010) | addTagByRequestId(request_id, id_project, tag) {
    method removeTagByRequestId (line 2073) | removeTagByRequestId(request_id, id_project, tag) {
    method addFollowerByRequestId (line 2152) | addFollowerByRequestId(request_id, id_project, member) {
    method setFollowersByRequestId (line 2244) | setFollowersByRequestId(request_id, id_project, newfollowers) {
    method removeFollowerByRequestId (line 2339) | removeFollowerByRequestId(request_id, id_project, member) {
    method getRequestParametersFromChatbot (line 2435) | async getRequestParametersFromChatbot(request_id) {
    method getConversationsCount (line 2458) | async getConversationsCount(id_project, status, preflight, hasBot, sta...
    method emitParticipantsEvents (line 2485) | emitParticipantsEvents(beforeRequest, requestComplete, oldParticipants) {

FILE: services/schemaMigrationService.js
  class SchamaMigrationService (line 9) | class SchamaMigrationService {
    method isEmptyObject (line 12) | isEmptyObject(obj) {
    method checkSchemaMigration (line 21) | async checkSchemaMigration(currentSchamaVersion) {

FILE: services/settingDataLoader.js
  class SettingDataLoader (line 9) | class SettingDataLoader {
    method save (line 12) | save() {

FILE: services/subscriptionNotifier.js
  class SubscriptionNotifier (line 41) | class SubscriptionNotifier {
    method findSubscriber (line 45) | findSubscriber(event, id_project) {
    method notify (line 63) | notify(subscriptions, payload, callback) {
    method decorate (line 171) | decorate(model, modelName) {
    method start (line 203) | start() {
    method subscribe (line 530) | subscribe(eventName, payload, callback ) {

FILE: services/subscriptionNotifierQueued.js
  class SubscriptionNotifierQueued (line 10) | class SubscriptionNotifierQueued {
    method start (line 12) | start() {

FILE: services/testWsService.js
  class TestWsService (line 5) | class TestWsService {
    method init (line 11) | init(server) {

FILE: services/trainingService.js
  class TrainingService (line 12) | class TrainingService {
    method train (line 15) | async train(eventName, id_faq_kb, webhook_enabled) {
    method start (line 112) | start() {

FILE: services/updateLeadQueued.js
  class UpdateLeadQueued (line 9) | class UpdateLeadQueued {
    method constructor (line 11) | constructor() {
    method listen (line 15) | listen() {
    method updateSnapshotLead (line 24) | updateSnapshotLead() {
    method sendMessageUpdateLead (line 72) | sendMessageUpdateLead() {

FILE: services/updateRequestSnapshotQueued.js
  class UpdateRequestSnapshotQueued (line 7) | class UpdateRequestSnapshotQueued {
    method constructor (line 9) | constructor() {
    method listen (line 13) | listen() {
    method updateRequestSnapshot (line 18) | updateRequestSnapshot() {

FILE: services/userService.js
  class UserService (line 6) | class UserService {
    method signup (line 8) | signup ( email, password, firstname, lastname, emailverified, phone) {

FILE: services/webhookService.js
  constant TILEBOT_ENDPOINT (line 10) | let TILEBOT_ENDPOINT = "http://localhost:" + port + "/modules/tilebot/";
  class WebhookService (line 16) | class WebhookService {
    method run (line 18) | async run(webhook, payload, dev, redis_client) {
    method generateChatbotToken (line 76) | async generateChatbotToken(chatbot) {

FILE: test/faqkbRoute.js
  class chatbot_service (line 621) | class chatbot_service {
    method getBotById (line 622) | async getBotById(id, published, api_url, chatbot_templates_api_url, to...
    method createBot (line 626) | async createBot(api_url, token, chatbot, project_id) {
    method importFaqs (line 630) | async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {
    method getBotById (line 687) | async getBotById(id, published, api_url, chatbot_templates_api_url, to...
    method createBot (line 693) | async createBot(api_url, token, chatbot, project_id) {
    method importFaqs (line 697) | async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {
    method getBotById (line 755) | async getBotById(id, published, api_url, chatbot_templates_api_url, to...
    method createBot (line 759) | async createBot(api_url, token, chatbot, project_id) {
    method importFaqs (line 763) | async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {
  class chatbot_service (line 686) | class chatbot_service {
    method getBotById (line 622) | async getBotById(id, published, api_url, chatbot_templates_api_url, to...
    method createBot (line 626) | async createBot(api_url, token, chatbot, project_id) {
    method importFaqs (line 630) | async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {
    method getBotById (line 687) | async getBotById(id, published, api_url, chatbot_templates_api_url, to...
    method createBot (line 693) | async createBot(api_url, token, chatbot, project_id) {
    method importFaqs (line 697) | async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {
    method getBotById (line 755) | async getBotById(id, published, api_url, chatbot_templates_api_url, to...
    method createBot (line 759) | async createBot(api_url, token, chatbot, project_id) {
    method importFaqs (line 763) | async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {
  class chatbot_service (line 754) | class chatbot_service {
    method getBotById (line 622) | async getBotById(id, published, api_url, chatbot_templates_api_url, to...
    method createBot (line 626) | async createBot(api_url, token, chatbot, project_id) {
    method importFaqs (line 630) | async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {
    method getBotById (line 687) | async getBotById(id, published, api_url, chatbot_templates_api_url, to...
    method createBot (line 693) | async createBot(api_url, token, chatbot, project_id) {
    method importFaqs (line 697) | async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {
    method getBotById (line 755) | async getBotById(id, published, api_url, chatbot_templates_api_url, to...
    method createBot (line 759) | async createBot(api_url, token, chatbot, project_id) {
    method importFaqs (line 763) | async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {

FILE: test/fileFilter.test.js
  function getMimeTypes (line 9) | function getMimeTypes(allowed_extension) {

FILE: test/mock/tdCacheMock.js
  class MockTdCache (line 2) | class MockTdCache {
    method constructor (line 4) | constructor() {
    method set (line 8) | async set(k, v) {
    method incr (line 15) | async incr(k) {
    method get (line 31) | async get(k) {
    method del (line 38) | async del(k) {

FILE: test/quoteManager.js
  function test (line 171) | async function test(date) {

FILE: test/webhookRoute.js
  class chatbot_service (line 546) | class chatbot_service {
    method fork (line 547) | async fork(id_faq_kb, api_url, token, project_id) {
    method setModified (line 552) | async setModified(chatbot_id, modified) {
  function send (line 662) | async function send(i) {

FILE: utils/TdCache.js
  class TdCache (line 3) | class TdCache {
    method constructor (line 5) | constructor(config) {
    method connect (line 12) | async connect(callback) {
    method set (line 41) | async set(key, value, options) {
    method setNX (line 83) | async setNX(key, value, ttlSeconds) {
    method incr (line 95) | async incr(key) {
    method incrby (line 110) | async incrby(key, increment) {
    method incrbyfloat (line 123) | async incrbyfloat(key, increment) {
    method hset (line 135) | async hset(dict_key, key, value, options) {
    method hdel (line 171) | async hdel(dict_key, key, options) {
    method setJSON (line 205) | async setJSON(key, value, options) {
    method get (line 210) | async get(key, callback) {
    method hgetall (line 226) | async hgetall(dict_key, callback) {
    method hget (line 246) | async hget(dict_key, key, callback) {
    method getJSON (line 266) | async getJSON(key, callback) {
    method del (line 271) | async del(key, callback) {
    method getClient (line 282) | getClient() {

FILE: utils/UIDGenerator.js
  class UIDGenerator (line 4) | class UIDGenerator {
    method generate (line 6) | generate() {

FILE: utils/aiUtils.js
  function loadMultiplier (line 10) | function loadMultiplier() {

FILE: utils/arrayUtil.js
  function arraysEqual (line 2) | function arraysEqual(a, b) {
  function parseStringArrayField (line 22) | function parseStringArrayField(field) {

FILE: utils/botFromParticipant.js
  class BotFromParticipant (line 4) | class BotFromParticipant {
    method getBotFromParticipants (line 7) | getBotFromParticipants(participants) {
    method getBotId (line 29) | getBotId(message) {

FILE: utils/commons/extend-query.js
  function hydrateModel (line 88) | function hydrateModel(constructor) {

FILE: utils/commons/q1.js
  function wait (line 60) | function wait () {

FILE: utils/commons/testperformance.js
  function wait (line 40) | function wait () {

FILE: utils/connectionUtil.js
  class ConnnectionUtil (line 2) | class ConnnectionUtil {
    method constructor (line 4) | constructor(connection) {
    method get (line 8) | get() {
    method set (line 12) | set(connection) {

FILE: utils/datesUtil.js
  class DatesUtil (line 3) | class DatesUtil {
    method parseDate (line 13) | parseDate(dateString, timezone) {
    method hasTimeComponent (line 70) | hasTimeComponent(dateString) {
    method convertLocalDatesToUTC (line 83) | convertLocalDatesToUTC(startDate, endDate, timezone) {
    method createDateRangeQuery (line 143) | createDateRangeQuery(startDate, endDate, timezone, fieldName = 'create...

FILE: utils/fileUtils.js
  class FileUtils (line 3) | class FileUtils {
    method downloadFromUrl (line 5) | async downloadFromUrl(url) {

FILE: utils/httpUtil.js
  class HttpUtil (line 14) | class HttpUtil {
    method call (line 16) | call(url, headers, json, method) {
    method post (line 34) | async post(url, payload, customHeaders, auth) {

FILE: utils/i8nUtil.js
  class I8nUtil (line 6) | class I8nUtil {
    method getMessage (line 9) | getMessage(key, lang, labelsObject) {

FILE: utils/jobs-worker-queue-manager/JobManagerV2.js
  class JobManager (line 3) | class JobManager {
    method constructor (line 4) | constructor(queueUrl, options) {
    method connectAndStartPublisher (line 20) | connectAndStartPublisher(callback) {
    method diconnectPublisher (line 59) | diconnectPublisher(callback) {
    method connectAndStartWorker (line 73) | connectAndStartWorker() {
    method publish (line 87) | publish(payload, callback) {
    method schedule (line 117) | schedule(fn, payload) {
    method run (line 136) | run(callback) {

FILE: utils/jobs-worker-queue-manager/queueManagerClassV2.js
  class QueueManager (line 6) | class QueueManager {
    method constructor (line 8) | constructor(url, options) {
    method connect (line 42) | connect(callback) {
    method close (line 82) | close(callback) {
    method whenConnected (line 90) | whenConnected(callback) {
    method startPublisher (line 98) | startPublisher(callback) {
    method publish (line 126) | publish(exchange, routingKey, content, callback) {
    method startWorker (line 160) | startWorker(callback) {
    method processMsg2 (line 222) | processMsg2(msg) {
    method closeOnErr (line 327) | closeOnErr(err) {
    method sendJson (line 334) | sendJson(data, topic, callback) {
    method send (line 343) | send(string, topic) {
    method on (line 348) | on(fn) {

FILE: utils/orgUtil.js
  class OrgUtil (line 11) | class OrgUtil {
    method constructor (line 13) | constructor() {
    method getOrg (line 28) | getOrg(req) {

FILE: utils/phoneUtil.js
  function normalizePhone (line 6) | function normalizePhone(phone) {

FILE: utils/project_userUtil.js
  class ProjectUserUtil (line 2) | class ProjectUserUtil {
    method isBusy (line 4) | isBusy(project_user, project_max_assigned_chat) {

FILE: utils/promiseUtil.js
  class PromiseUtil (line 1) | class PromiseUtil {
    method doAllSequentually (line 3) | async doAllSequentually(promises) {

FILE: utils/recipientEmailUtil.js
  class RecipientEmailUtil (line 6) | class RecipientEmailUtil {
    method iterateProjectUser (line 8) | iterateProjectUser(project_users) {
    method process (line 21) | async process(to, projectid) {

FILE: utils/requestUtil.js
  class RequestUtil (line 2) | class RequestUtil {
    method getToken (line 4) | getToken(headers) {
    method getProjectIdFromRequestId (line 17) | getProjectIdFromRequestId(request_id) {
    method arraysEqual (line 33) | arraysEqual (a, b) {

FILE: utils/segment2mongoConverter.js
  class Segment2MongoConverter (line 2) | class Segment2MongoConverter {
    method convert (line 4) | convert(query, segment) {
    method convertEqualsOperatorFilter (line 63) | convertEqualsOperatorFilter(query, filter) {
    method convertNotEqualsOperatorFilter (line 67) | convertNotEqualsOperatorFilter(query, filter) {
    method convertGreaterThanOperatorFilter (line 71) | convertGreaterThanOperatorFilter(query, filter) {
    method convertGreaterThanOrEqualOperatorFilter (line 74) | convertGreaterThanOrEqualOperatorFilter(query, filter) {
    method convertStartWithOperatorFilter (line 78) | convertStartWithOperatorFilter(query, filter) {
    method convertStartWithOperatorFilter (line 81) | convertStartWithOperatorFilter(query, filter) {
    method convertContainsOperatorFilter (line 84) | convertContainsOperatorFilter(query, filter) {
    method convertIsUndefinedOperatorFilter (line 87) | convertIsUndefinedOperatorFilter(query, filter) {
    method convertExistsOperatorFilter (line 90) | convertExistsOperatorFilter(query, filter) {

FILE: utils/sendEmailUtil.js
  class SendEmailUtil (line 6) | class SendEmailUtil {
    method sendEmailDirect (line 7) | async sendEmailDirect(to, text, id_project, recipient, subject, messag...

FILE: utils/sendMessageUtil.js
  class SendMessageUtil (line 10) | class SendMessageUtil {
    method send (line 12) | async send(sender, senderFullname, recipient, text, id_project, create...

FILE: utils/stringUtil.js
  class StringUtil (line 2) | class StringUtil {
    method replaceAll (line 4) | replaceAll(string, characterToReplace, replacement) {

FILE: utils/userUtil.js
  class UserUtil (line 2) | class UserUtil {
    method decorateUser (line 4) | decorateUser(user) {

FILE: utils/winston-mongodb/helpers.js
  function cloneMeta (line 28) | function cloneMeta(node, opt_parents) {

FILE: utils/winston-mongodb/winston-mongodb.d.ts
  type Transports (line 17) | interface Transports extends WinstonMongoDBTransports {}
  type MongoDBTransportInstance (line 21) | interface MongoDBTransportInstance extends transports.StreamTransportIns...
  type WinstonMongoDBTransports (line 26) | interface WinstonMongoDBTransports {
  type MongoDBConnectionOptions (line 36) | interface MongoDBConnectionOptions {

FILE: utils/winston-mongodb/winston-mongodb.js
  function setupDatabaseAndEmptyQueue (line 90) | function setupDatabaseAndEmptyQueue(db) {
  function processOpQueue (line 101) | function processOpQueue() {
  function createCollection (line 106) | function createCollection(db) {
  function connectToDatabase (line 135) | function connectToDatabase(logger) {
  function next (line 406) | function next() {

FILE: websocket/pubsub.js
  class PubSub (line 17) | class PubSub {
    method constructor (line 19) | constructor (wss, callbacksArg) {
    method load (line 40) | load() {
    method ping (line 118) | ping(ws) {
    method handleAddSubscription (line 143) | handleAddSubscription (topic, clientId) {
    method handleUnsubscribe (line 172) | handleUnsubscribe (topic, clientId) {
    method handlePublishMessage (line 233) | handlePublishMessage (topic, message, from, isBroadcast = false, metho...
    method handlePublishMessageToClientId (line 275) | handlePublishMessageToClientId (topic, message, clientId, method) {
    method handleReceivedClientMessage (line 294) | async handleReceivedClientMessage (clientId, message, req) {
    method stringToJson (line 476) | stringToJson (message) {
    method addClient (line 491) | addClient (client) {
    method removeClient (line 505) | removeClient (id) {
    method getClient (line 514) | getClient (id) {
    method autoId (line 523) | autoId () {
    method send (line 531) | send (clientId, message) {

FILE: websocket/subscription.js
  class Subscription (line 6) | class Subscription {
    method constructor (line 8) | constructor () {
    method get (line 17) | get (id) {
    method add (line 28) | add (topic, clientId, type = 'ws') {
    method remove (line 58) | remove (id) {
    method clear (line 66) | clear () {
    method getSubscriptions (line 76) | getSubscriptions (predicate = null) {
    method autoId (line 87) | autoId () {

FILE: websocket/webSocketServer.js
  function logInvalidToken (line 41) | function logInvalidToken(req, err) {
  class WebSocketServer (line 81) | class WebSocketServer {
    method constructor (line 83) | constructor() {
    method init (line 90) | init(server) {
Condensed preview — 506 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (4,001K chars).
[
  {
    "path": ".circleci/config.yml",
    "chars": 2610,
    "preview": "# Use the latest 2.1 version of CircleCI pipeline process engine. \n# See: https://circleci.com/docs/2.0/configuration-re"
  },
  {
    "path": ".dockerignore",
    "chars": 65,
    "preview": "node_modules\nnpm-debug.log\n.firebasekey.json\nconfig/.firebase-key"
  },
  {
    "path": ".github/workflows/docker-community-profiler-latest.yml",
    "chars": 702,
    "preview": "name: Docker Image Community Profiler latest CI\n\non:\t\n  push:\t \n    branches: [ master ]\n  pull_request:\t\n    branches: "
  },
  {
    "path": ".github/workflows/docker-community-push-latest.yml",
    "chars": 670,
    "preview": "name: Docker Image Community latest CI\n\non:\t\n  push:\t \n    branches: [ master ]\n  pull_request:\t\n    branches: [ master "
  },
  {
    "path": ".github/workflows/docker-community-worker-push-latest.yml",
    "chars": 693,
    "preview": "name: Docker Image Community Worker latest CI\n\non:\t\n  push:\t \n    branches: [ master ]\n  pull_request:\t\n    branches: [ "
  },
  {
    "path": ".github/workflows/docker-image-en-tag-push.yml",
    "chars": 1581,
    "preview": "name: Publish Docker Enterprise Tag image\n\non:\n  push:\n    tags:\n      - '**'           # Push events to every tag inclu"
  },
  {
    "path": ".github/workflows/docker-image-tag-community-tag-push.yml",
    "chars": 940,
    "preview": "name: Publish Docker Community image tags\n\non:\n  push:\n    tags:\n      - '**'           # Push events to every tag inclu"
  },
  {
    "path": ".github/workflows/docker-image-tag-worker-community-tag-push.yml",
    "chars": 957,
    "preview": "name: Publish Docker Community Worker image tags\n\non:\n  push:\n    tags:\n      - '**'           # Push events to every ta"
  },
  {
    "path": ".github/workflows/docker-push-en-push-latest.yml",
    "chars": 750,
    "preview": "name: Docker Enterprise Latest Image CI\n\non:\t\n  push:\t \n    branches: [ master ]\n  pull_request:\t\n    branches: [ master"
  },
  {
    "path": ".gitignore",
    "chars": 108,
    "preview": "node_modules\nconfig/.firebase-key\n.env.list\ndata\n.firebasekey-pre.json\n.firebasekey.json\n.env\ncopyfiles\nlogs"
  },
  {
    "path": ".glitch-assets",
    "chars": 0,
    "preview": ""
  },
  {
    "path": ".npmrc_",
    "chars": 46,
    "preview": "//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n"
  },
  {
    "path": ".travis.yml",
    "chars": 1181,
    "preview": "language: node_js\ndist: bionic\nnode_js:\n- '12.20.2'\nservices: mongodb\nsudo: required\ncache:\n  directories:\n  - node_modu"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 74444,
    "preview": "\n\n💥 TILEDESK SERVER v2.3.77 💥\n🚀        TAGGED AND PUBLISHED ON NPM           🚀\n🚀        IN PRODUCTION                   "
  },
  {
    "path": "Dockerfile",
    "chars": 562,
    "preview": "FROM node:18-bullseye\n\nRUN sed -i 's/stable\\/updates/stable-security\\/updates/' /etc/apt/sources.list\n\n\nRUN apt-get upda"
  },
  {
    "path": "Dockerfile-en",
    "chars": 670,
    "preview": "FROM node:18-bullseye\n\nRUN sed -i 's/stable\\/updates/stable-security\\/updates/' /etc/apt/sources.list\n\nRUN apt-get updat"
  },
  {
    "path": "Dockerfile-jobs",
    "chars": 564,
    "preview": "FROM node:18-bullseye\n\nRUN sed -i 's/stable\\/updates/stable-security\\/updates/' /etc/apt/sources.list\n\nRUN apt-get updat"
  },
  {
    "path": "Dockerfile-profiler",
    "chars": 576,
    "preview": "FROM node:18-bullseye\n\nRUN sed -i 's/stable\\/updates/stable-security\\/updates/' /etc/apt/sources.list\n\n\nRUN apt-get upda"
  },
  {
    "path": "LICENSE",
    "chars": 1074,
    "preview": "MIT License\n\nCopyright (c) 2020 Tiledesk\n\nPermission is hereby granted, free of charge, \nto any person obtaining a copy "
  },
  {
    "path": "README.md",
    "chars": 4688,
    "preview": "[![npm version](https://badge.fury.io/js/%40tiledesk%2Ftiledesk-server.svg)](https://badge.fury.io/js/%40tiledesk%2Ftile"
  },
  {
    "path": "app.js",
    "chars": 26629,
    "preview": "var dotenvPath = undefined;\n\nif (process.env.DOTENV_PATH) {\n  dotenvPath = process.env.DOTENV_PATH;\n  console.log(\"load "
  },
  {
    "path": "app.json",
    "chars": 1957,
    "preview": "{\n  \"name\": \"Tiledesk Server\",\n  \"description\": \"Tildesk server\",\n  \"repository\": \"https://github.com/Tiledesk/tiledesk-"
  },
  {
    "path": "archive.sh",
    "chars": 2848,
    "preview": "#!/bin/bash\n\n# ================================\n# Script interattivo per archiviare branch\n# ==========================="
  },
  {
    "path": "bin/www",
    "chars": 2138,
    "preview": "#!/usr/bin/env node\n\n/**\n * Module dependencies.\n */\n\nvar app = require('../app');\nvar debug = require('debug')('tiledes"
  },
  {
    "path": "channels/channelManager.js",
    "chars": 2797,
    "preview": "\nvar winston = require('../config/winston');\n\n\nvar chat21Enabled = process.env.CHAT21_ENABLED;\nwinston.debug(\"chat21Enab"
  },
  {
    "path": "channels/chat21/chat21Client.js",
    "chars": 503,
    "preview": "\nvar chat21Config = require('../../channels/chat21/chat21Config');\nvar Chat21 = require('@chat21/chat21-node-sdk');\nvar "
  },
  {
    "path": "channels/chat21/chat21Config.js",
    "chars": 133,
    "preview": "module.exports = {\n  'url': 'https://CHANGEIT.cloudfunctions.net',\n  'appid': 'tilechat',\n  'adminToken' : 'chat21-secre"
  },
  {
    "path": "channels/chat21/chat21Contact.js",
    "chars": 6801,
    "preview": "var express = require('express');\nvar router = express.Router();\nvar mongoose = require('mongoose');\n\nvar Project_user ="
  },
  {
    "path": "channels/chat21/chat21Event.js",
    "chars": 157,
    "preview": "const EventEmitter = require('events');\n\nclass Chat21Event extends EventEmitter {}\n\nconst chat21Event = new Chat21Event("
  },
  {
    "path": "channels/chat21/chat21Handler.js",
    "chars": 50805,
    "preview": "\nconst messageEvent = require('../../event/messageEvent');\nconst authEvent = require('../../event/authEvent');\nconst bot"
  },
  {
    "path": "channels/chat21/chat21Util.js",
    "chars": 8192,
    "preview": "\n\n\n// ***** UNUSED\nclass Chat21Util {\n    \n    getButtonFromText(message, bot, qna) { \n            var that = this;\n    "
  },
  {
    "path": "channels/chat21/chat21WebHook.js",
    "chars": 36073,
    "preview": "var express = require('express');\nvar router = express.Router();\nvar Request = require(\"../../models/request\");\nvar requ"
  },
  {
    "path": "channels/chat21/configRoute.js",
    "chars": 1042,
    "preview": "var express = require('express');\nvar router = express.Router();\n// var Setting = require(\"../models/setting\");\n\n\n\nroute"
  },
  {
    "path": "channels/chat21/firebaseConfig.js",
    "chars": 76,
    "preview": "module.exports = {\n  'databaseUrl': 'https://chat-v2-dev.firebaseio.com'\n};\n"
  },
  {
    "path": "channels/chat21/firebaseConnector.js",
    "chars": 2611,
    "preview": "'use strict';\n\nvar winston = require('../../config/winston');\n\nconst MaskData = require(\"maskdata\");\n\nconst maskOptions "
  },
  {
    "path": "channels/chat21/firebaseService.js",
    "chars": 1195,
    "preview": "var admin = require('./firebaseConnector');\n\n\nvar winston = require('../../config/winston');\n\nclass FirebaseService {\n\n "
  },
  {
    "path": "channels/chat21/firebaseauth.js",
    "chars": 812,
    "preview": "\n'use strict';\n\nconst express = require('express');\nvar router = express.Router();\nvar firebaseService = require(\"./fire"
  },
  {
    "path": "channels/chat21/nativeauth.js",
    "chars": 3058,
    "preview": "\n'use strict';\n\nconst express = require('express');\nvar router = express.Router();\nvar winston = require('../../config/w"
  },
  {
    "path": "channels/chat21/package.json",
    "chars": 492,
    "preview": "{\n  \"name\": \"@tiledesk/tiledesk-chat21-app\",\n  \"description\": \"The Tiledesk Chat21 module\",\n  \"version\": \"1.1.8\",\n  \"pri"
  },
  {
    "path": "channels/chat21/test-int/chat21Handler.js",
    "chars": 6043,
    "preview": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nrequire('dotenv').config();\n\nvar expec"
  },
  {
    "path": "channels/chat21/test-int/chat21WebHook.js",
    "chars": 25048,
    "preview": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nlet mongoose = require(\"mongoose\");\nva"
  },
  {
    "path": "channels/chat21/tiledesk-util.js",
    "chars": 5664,
    "preview": "/* \n    ver 0.8.3\n    Andrea Sponziello - (c) Tiledesk.com\n*/\nvar winston = require('../../config/winston');\n\nclass Tile"
  },
  {
    "path": "config/cache.js",
    "chars": 52,
    "preview": "module.exports = {\n    'defaultTTL':120,    \n  };\n  "
  },
  {
    "path": "config/database.js",
    "chars": 240,
    "preview": "module.exports = {\n  secret:'nodeauthsecret',\n  schemaVersion: 2111, \n  database: 'mongodb://localhost:27017/tiledesk',\n"
  },
  {
    "path": "config/email.js",
    "chars": 287,
    "preview": "module.exports = {\n  'host':'smtp.mailgun.org',\n  'username': 'postmaster@mg.tiledesk.com',\n  'from': 'Tiledesk Notifica"
  },
  {
    "path": "config/global.js",
    "chars": 129,
    "preview": "module.exports = {\n  'apiUrl':'http://localhost:3000',\n  'organizationBaseUrl' : 'org.local',\n  'organizationEnabled' : "
  },
  {
    "path": "config/kb/embedding.js",
    "chars": 264,
    "preview": "module.exports = {\n    provider: process.env.EMBEDDINGS_PROVIDER || \"openai\",\n    name: process.env.EMBEDDINGS_NAME || \""
  },
  {
    "path": "config/kb/engine.hybrid.js",
    "chars": 585,
    "preview": "module.exports = {\n    name: process.env.VECTOR_STORE_NAME || 'pinecone',\n    type: process.env.INDEX_TYPE_HYBRID || pro"
  },
  {
    "path": "config/kb/engine.js",
    "chars": 543,
    "preview": "module.exports = {\n    name: process.env.VECTOR_STORE_NAME || 'pinecone',\n    type: process.env.INDEX_TYPE || process.en"
  },
  {
    "path": "config/kb/prompt/rag/PromptManager.js",
    "chars": 1516,
    "preview": "const fs = require('fs');\nconst path = require('path');\n\nconst modelMap = {\n    \"gpt-3.5-turbo\":        \"gpt-3.5.txt\",\n "
  },
  {
    "path": "config/kb/prompt/rag/general.txt",
    "chars": 502,
    "preview": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n\n1. Answer in the same language"
  },
  {
    "path": "config/kb/prompt/rag/gpt-3.5.txt",
    "chars": 247,
    "preview": "You are an helpful assistant for question-answering tasks.\n\nUse ONLY the pieces of retrieved context delimited by #### a"
  },
  {
    "path": "config/kb/prompt/rag/gpt-4.1.txt",
    "chars": 501,
    "preview": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n\n1. Answer in the same language"
  },
  {
    "path": "config/kb/prompt/rag/gpt-4.txt",
    "chars": 349,
    "preview": "You are an helpful assistant for question-answering tasks.\n\nUse ONLY the pieces of retrieved context delimited by #### a"
  },
  {
    "path": "config/kb/prompt/rag/gpt-4o.txt",
    "chars": 477,
    "preview": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n\n1. Answer in the same language"
  },
  {
    "path": "config/kb/prompt/rag/gpt-5.txt",
    "chars": 1066,
    "preview": "# ROLE\nYou are an AI assistant that answers the user's question using only the information contained in the provided con"
  },
  {
    "path": "config/kb/prompt/rag/gpt-5.x.txt",
    "chars": 1066,
    "preview": "# ROLE\nYou are an AI assistant that answers the user's question using only the information contained in the provided con"
  },
  {
    "path": "config/kb/situatedContext.js",
    "chars": 220,
    "preview": "module.exports = {\n    enable: process.env.SITUATED_CONTEXT_ENABLE === \"true\",\n    provider: process.env.SITUATED_CONTEX"
  },
  {
    "path": "config/labels/widget.json",
    "chars": 88740,
    "preview": "[\n    {\n        \"lang\": \"EN\",\n        \"data\":{\n            \"LABEL_PLACEHOLDER\": \"type your message..\",\n            \"LABE"
  },
  {
    "path": "config/widget.js",
    "chars": 104,
    "preview": "module.exports = {\n  'location':'http://localhost:4200/',\n  'testLocation':'http://localhost:4200/',\n};\n"
  },
  {
    "path": "config/winston-mt-multilogger.js",
    "chars": 1621,
    "preview": "// UNUSED\nvar appRoot = require('app-root-path');\nvar winston = require('winston');\nvar config = require('./database');\n"
  },
  {
    "path": "config/winston-mt.js",
    "chars": 1388,
    "preview": "// UNUSED\nvar appRoot = require('app-root-path');\nvar winston = require('winston');\nvar config = require('./database');\n"
  },
  {
    "path": "config/winston.js",
    "chars": 3318,
    "preview": "var appRoot = require('app-root-path');\nvar winston = require('winston');\nvar config = require('./database');\n\nvar level"
  },
  {
    "path": "deploy-beta.sh",
    "chars": 316,
    "preview": "npm version prerelease --preid=beta\nversion=`node -e 'console.log(require(\"./package.json\").version)'`\necho \"version $ve"
  },
  {
    "path": "deploy.sh",
    "chars": 307,
    "preview": "git pull\nnpm version patch\nversion=`node -e 'console.log(require(\"./package.json\").version)'`\necho \"version $version\"\n\ni"
  },
  {
    "path": "deploynew.sh",
    "chars": 1508,
    "preview": "#!/bin/bash\n\n# Load .env variables\nif [ -f .env ]; then\n  export $(grep -v '^#' .env | xargs)\nfi\n\n# Check if the token i"
  },
  {
    "path": "docs/api-dev.md",
    "chars": 51695,
    "preview": "# TILEDESK LOCALHOST REST API\n\n## Signup\n\n```\ncurl -v -X POST -H 'Content-Type:application/json' -d '{\"firstname\":\"Andre"
  },
  {
    "path": "docs/api-mgm.md",
    "chars": 93,
    "preview": "# TILEDESK REST API\n\nPlease refer to the [Developer Portal](https://developer.tiledesk.com/)\n"
  },
  {
    "path": "docs/deploy.md",
    "chars": 2921,
    "preview": "\n# Docker compose\n\n## Installation\n\n```\nsudo curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-c"
  },
  {
    "path": "docs/performance.md",
    "chars": 5799,
    "preview": "loadtest http://localhost:7357/ -t 20 -c 10\n\n\n\n\n\n\n\n\n\n// db.getCollection('requests').find({\"id_project\":\"5ea800091147f28"
  },
  {
    "path": "docs/routes-answered.md",
    "chars": 5512,
    "preview": "# Route `kb/answered` — domande con risposta (Knowledge Base)\n\nDocumentazione per [`routes/answered.js`](../routes/answe"
  },
  {
    "path": "docs/testing.md",
    "chars": 849,
    "preview": "== \n\nnpm run test:int\n\n== Run specific test ==\n\nnpm test --  --grep 'Subscription./requests.create'\n\n\n\nmocha trigger.js "
  },
  {
    "path": "docs/upgrading.md",
    "chars": 994,
    "preview": "# Upgrading\n\nUse this procedure to upgrade from a previous version of Tiledesk server. You must manually execute the fol"
  },
  {
    "path": "errorCodes.js",
    "chars": 829,
    "preview": "const errorCodes = {\n  AUTH: {\n    BASE_CODE: 10000,\n    ERRORS: {\n      USER_NOT_FOUND: 10001,\n      //AUTH_FAILED: 100"
  },
  {
    "path": "event/authEvent.js",
    "chars": 858,
    "preview": "const EventEmitter = require('events');\nvar RoleConstants = require(\"../models/roleConstants\");\n\nclass AuthEvent extends"
  },
  {
    "path": "event/botEvent.js",
    "chars": 8438,
    "preview": "const EventEmitter = require('events');\nconst messageEvent = require('../event/messageEvent');\nconst Faq_kb = require('."
  },
  {
    "path": "event/connectionEvent.js",
    "chars": 170,
    "preview": "const EventEmitter = require('events');\n\nclass ConnectionEvent extends EventEmitter {}\n\nconst connectionEvent = new Conn"
  },
  {
    "path": "event/departmentEvent.js",
    "chars": 995,
    "preview": "const EventEmitter = require('events');\n\nclass DepartmentEvent extends EventEmitter {}\nvar winston = require('../config/"
  },
  {
    "path": "event/emailEvent.js",
    "chars": 2369,
    "preview": "const EventEmitter = require('events');\nconst project_user = require('../models/project_user');\nvar winston = require('."
  },
  {
    "path": "event/faqBotEvent.js",
    "chars": 157,
    "preview": "const EventEmitter = require('events');\n\nclass FaqBotEvent extends EventEmitter {}\n\nconst faqBotEvent = new FaqBotEvent("
  },
  {
    "path": "event/groupEvent.js",
    "chars": 152,
    "preview": "const EventEmitter = require('events');\n\nclass GroupEvent extends EventEmitter {}\n\nconst groupEvent = new GroupEvent();\n"
  },
  {
    "path": "event/integrationEvent.js",
    "chars": 262,
    "preview": "const EventEmitter = require('events');\n\nlet winston = require('../config/winston');\n\nclass IntegrationEvent extends Eve"
  },
  {
    "path": "event/labelEvent.js",
    "chars": 153,
    "preview": "const EventEmitter = require('events');\n\nclass LabelEvent extends EventEmitter {}\n\nconst labelEvent = new LabelEvent();\n"
  },
  {
    "path": "event/leadEvent.js",
    "chars": 262,
    "preview": "const EventEmitter = require('events');\n\nclass LeadEvent extends EventEmitter {\n    constructor() {\n        super();\n   "
  },
  {
    "path": "event/message2Event.js",
    "chars": 1701,
    "preview": "var winston = require('../config/winston');\nvar MessageConstants = require(\"../models/messageConstants\");\n// var message"
  },
  {
    "path": "event/messageEvent.js",
    "chars": 11888,
    "preview": "const EventEmitter = require('events');\nvar winston = require('../config/winston');\nvar Request = require(\"../models/req"
  },
  {
    "path": "event/messagePromiseEvent.js",
    "chars": 197,
    "preview": "const EventEmitter = require('promise-events');\n\nclass MessagePromiseEvent extends EventEmitter {}\n\nconst messagePromise"
  },
  {
    "path": "event/projectEvent.js",
    "chars": 161,
    "preview": "const EventEmitter = require('events');\n\nclass ProjectEvent extends EventEmitter {}\n\nconst projectEvent = new ProjectEve"
  },
  {
    "path": "event/projectUserEvent.js",
    "chars": 1186,
    "preview": "const EventEmitter = require('events');\nconst winston = require('../config/winston');\nconst Group = require(\"../models/g"
  },
  {
    "path": "event/requestEvent.js",
    "chars": 2239,
    "preview": "const EventEmitter = require('events');\nvar Message = require(\"../models/message\");\nvar Request = require(\"../models/req"
  },
  {
    "path": "event/roleEvent.js",
    "chars": 148,
    "preview": "const EventEmitter = require('events');\n\nclass RoleEvent extends EventEmitter {}\n\nconst roleEvent = new RoleEvent();\n\n\n\n"
  },
  {
    "path": "event/subscriptionEvent.js",
    "chars": 225,
    "preview": "const EventEmitter = require('events');\n\nclass SubscriptionEvent extends EventEmitter {}\nvar winston = require('../confi"
  },
  {
    "path": "jobs.js",
    "chars": 4360,
    "preview": "\nvar dotenvPath = undefined;\n\nif (process.env.DOTENV_PATH) {\n  dotenvPath = process.env.DOTENV_PATH;\n  console.log(\"load"
  },
  {
    "path": "jobsManager.js",
    "chars": 4563,
    "preview": "\nvar winston = require('./config/winston');\n\nclass JobsManager {\n    constructor(jobWorkerEnabled, geoService, botEvent,"
  },
  {
    "path": "middleware/fetchLabels.js",
    "chars": 907,
    "preview": "\nvar fs = require('fs');\nvar path = require('path');\nvar winston = require('../config/winston');\n\n\nvar labelsDir = __dir"
  },
  {
    "path": "middleware/file-type.js",
    "chars": 6402,
    "preview": "const FileType = require('file-type');\nconst fs = require('fs');\n\n// List of text-based MIME types that FileType cannot "
  },
  {
    "path": "middleware/has-role.js",
    "chars": 10669,
    "preview": "var Faq_kb = require(\"../models/faq_kb\");\nvar Subscription = require(\"../models/subscription\");\nvar winston = require('."
  },
  {
    "path": "middleware/ipFilter.js",
    "chars": 5621,
    "preview": "const ipfilter = require('express-ipfilter').IpFilter\nvar winston = require('../config/winston');\nvar jwt = require('jso"
  },
  {
    "path": "middleware/noentitycheck.js",
    "chars": 282,
    "preview": "var winston = require('../config/winston');\n\n\n\nmodule.exports = \n    function(req,res,next){ \n        winston.debug(\"bef"
  },
  {
    "path": "middleware/passport.js",
    "chars": 32362,
    "preview": "var passportJWT = require(\"passport-jwt\");\nvar JwtStrategy = passportJWT.Strategy;\nvar ExtractJwt = passportJWT.ExtractJ"
  },
  {
    "path": "middleware/recaptcha.js",
    "chars": 1128,
    "preview": "var winston = require('../config/winston');\n\nvar Recaptcha = require('express-recaptcha').RecaptchaV3;\n\nconst recaptcha_"
  },
  {
    "path": "middleware/valid-token.js",
    "chars": 604,
    "preview": "\nmodule.exports = function(req, res, next) {\n        // winston.debug(\"valid-token\");\n        var token = getToken(req.h"
  },
  {
    "path": "migrations/1601628781595-project_users_presence.js",
    "chars": 788,
    "preview": "var Project_user = require(\"../models/project_user\");\nvar winston = require('../config/winston');\n\n/**\n * Make any chang"
  },
  {
    "path": "migrations/1602847963299-message-channel_type-and-channel-fields-added--autosync.js",
    "chars": 787,
    "preview": "var Message = require(\"../models/message\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you nee"
  },
  {
    "path": "migrations/1603797978971-project_users-status-field-added--autosync.js",
    "chars": 772,
    "preview": "var Project_user = require(\"../models/project_user\");\nvar winston = require('../config/winston');\n\n/**\n * Make any chang"
  },
  {
    "path": "migrations/1603955232377-requests-channel-outbound-fields--autosync.js",
    "chars": 788,
    "preview": "var Request = require(\"../models/request\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you nee"
  },
  {
    "path": "migrations/1603955232378-requests-channel-fields--autosync.js",
    "chars": 772,
    "preview": "var Request = require(\"../models/request\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you nee"
  },
  {
    "path": "migrations/1604082287722-labels-data-default-fields--autosync.js",
    "chars": 2579,
    "preview": "var Label = require(\"../models/label\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to"
  },
  {
    "path": "migrations/1604082288723-labels-waiting_time-added_suffix_reply_time--autosync.js",
    "chars": 2379,
    "preview": "var Label = require(\"../models/label\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to"
  },
  {
    "path": "migrations/1611576399823-project-settings-max_agent_assigned_renamed--autosync.js",
    "chars": 963,
    "preview": "var Project = require(\"../models/project\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you nee"
  },
  {
    "path": "migrations/1611576534533-project_user-max_assigned_chat-renamed--autosync.js",
    "chars": 991,
    "preview": "var Project_user = require(\"../models/project_user\");\nvar winston = require('../config/winston');\n\n/**\n * Make any chang"
  },
  {
    "path": "migrations/1615214914082-faq-intent_id-intent_display_name-fields-added--autosync.js",
    "chars": 1149,
    "preview": "var Faq = require(\"../models/faq\");\nvar winston = require('../config/winston');\nvar { nanoid } = require(\"nanoid\");\ncons"
  },
  {
    "path": "migrations/1616685902635-request_agents_to_snapshot_agents--autosync.js",
    "chars": 1753,
    "preview": "var Request = require(\"../models/request\");\nvar winston = require('../config/winston');\nconst request = require(\"../mode"
  },
  {
    "path": "migrations/1616687831941-trigger_availableAgentsCount_to_snapshot_agents--autosync.js",
    "chars": 2863,
    "preview": "// var Trigger = require(\"../models/request\");\nvar mongoose = require('mongoose');\n\nvar winston = require('../config/win"
  },
  {
    "path": "migrations/1616687831941-trigger_availableAgentsCount_to_snapshot_agents-key--autosync.js",
    "chars": 2834,
    "preview": "// var Trigger = require(\"../models/request\");\nvar mongoose = require('mongoose');\n\nvar winston = require('../config/win"
  },
  {
    "path": "migrations/1619185894304-request-remove-duplicated-request-by-request_id--autosync.js",
    "chars": 1834,
    "preview": "var mongoose = require('mongoose');\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to mak"
  },
  {
    "path": "migrations/1752742733903-namespace-engine-migration.js",
    "chars": 807,
    "preview": "var winston = require('../config/winston');\nconst { Namespace } = require('../models/kb_setting');\n\nasync function up ()"
  },
  {
    "path": "migrations/1757601159298-project_user_role_type.js",
    "chars": 3109,
    "preview": "var Project_user = require(\"../models/project_user\");\nvar winston = require('../config/winston');\n\nconst BATCH_SIZE = 10"
  },
  {
    "path": "migrations/1771844588961-phone-channels-migration.js",
    "chars": 4022,
    "preview": "var winston = require('../config/winston');\nconst Request = require('../models/request');\nconst phoneUtil = require('../"
  },
  {
    "path": "models/actionsConstants.js",
    "chars": 117,
    "preview": "module.exports = {\n    CHAT_ACTION_MESSAGE : {\n        AGENT : \"\\\\agent\",\n        CLOSE : \"\\\\close\",    \n    }\n}\n    "
  },
  {
    "path": "models/analyticMessagesResult.js",
    "chars": 395,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar Analy"
  },
  {
    "path": "models/analyticProject_usersResult.js",
    "chars": 419,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar Analy"
  },
  {
    "path": "models/analyticResult.js",
    "chars": 940,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n// mongoose.set('debug', true);\nvar winston = require("
  },
  {
    "path": "models/analytics.js",
    "chars": 489,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n\nvar AnalyticsSchema = new Schema({\n  id_project: {\n  "
  },
  {
    "path": "models/auth.js",
    "chars": 1643,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar bcrypt = require('bcrypt-nodejs');\n\nvar AuthSchema"
  },
  {
    "path": "models/bot.1.js",
    "chars": 631,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n\n// var uuid = require('node-uuid');\n// require('mongo"
  },
  {
    "path": "models/channel.js",
    "chars": 422,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar Chan"
  },
  {
    "path": "models/channelConstants.js",
    "chars": 224,
    "preview": "module.exports = {\n        CHAT21 : 'chat21',\n        FACEBOOK : 'facebook',\n        TELEGRAM : 'telegram',\n        WHAT"
  },
  {
    "path": "models/chatbotTemplates.js",
    "chars": 568,
    "preview": "module.exports = {\n    chatbot: {\n        default: 'blank',\n        templates: ['empty', 'blank', 'handoff', 'example']\n"
  },
  {
    "path": "models/chatbotTypes.js",
    "chars": 112,
    "preview": "module.exports = {\n    CHATBOT: 'chatbot',\n    WEBHOOK: 'webhook',\n    COPILOT: 'copilot',\n    VOICE: 'voice'\n}\n"
  },
  {
    "path": "models/contact.js",
    "chars": 417,
    "preview": "const mongoose = require('mongoose');\nconst Schema = mongoose.Schema;\n\nconst ContactSchema = new Schema({\n  phone: {\n   "
  },
  {
    "path": "models/department.js",
    "chars": 2092,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar routingConstants = require('../models/routingConst"
  },
  {
    "path": "models/faq.js",
    "chars": 5061,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar { nano"
  },
  {
    "path": "models/faq_kb.js",
    "chars": 5906,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nconst uuidv4 = require('uuid/v4');\nvar winston = requi"
  },
  {
    "path": "models/firebaseSetting.js",
    "chars": 530,
    "preview": "// var mongoose = require('mongoose');\n// var Schema = mongoose.Schema;\n\n// var FirebaseSettingSchema = new Schema({\n  \n"
  },
  {
    "path": "models/flowLogs.js",
    "chars": 1588,
    "preview": "const mongoose = require('mongoose');\nconst Schema = mongoose.Schema;\n\nconst RowSchema = new Schema(\n  {\n    text: {\n   "
  },
  {
    "path": "models/group.js",
    "chars": 1646,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar Grou"
  },
  {
    "path": "models/groupMemberSchama.js",
    "chars": 310,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar Group"
  },
  {
    "path": "models/integrations.js",
    "chars": 462,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar Integ"
  },
  {
    "path": "models/kbConstants.js",
    "chars": 163,
    "preview": "const kbTypes = {\n        URL: 'url',\n        TEXT: 'text',\n        FAQ: 'faq',\n        PDF: 'pdf',\n        DOCX: 'docx'"
  },
  {
    "path": "models/kb_setting.js",
    "chars": 5970,
    "preview": "let mongoose = require('mongoose');\nlet Schema = mongoose.Schema;\nlet winston = require('../config/winston');\n\nconst DEF"
  },
  {
    "path": "models/label.js",
    "chars": 1034,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar Labe"
  },
  {
    "path": "models/labelSingle.js",
    "chars": 648,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar Labe"
  },
  {
    "path": "models/lead.js",
    "chars": 2443,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar LeadCo"
  },
  {
    "path": "models/leadConstants.js",
    "chars": 84,
    "preview": "module.exports = {\n        TEMP : 50,\n        NORMAL : 100,\n        DELETED: 1000\n}\n"
  },
  {
    "path": "models/location.js",
    "chars": 1028,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar Locat"
  },
  {
    "path": "models/message.js",
    "chars": 2772,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar Channe"
  },
  {
    "path": "models/messageConstants.js",
    "chars": 2359,
    "preview": "module.exports = {\n    CHAT_MESSAGE_STATUS : {\n        FAILED : -100,\n        SENDING : 0,\n        SENT : 100, //saved i"
  },
  {
    "path": "models/note.js",
    "chars": 505,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar Note"
  },
  {
    "path": "models/openai_kbs.js",
    "chars": 505,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar Opena"
  },
  {
    "path": "models/pending-invitation.js",
    "chars": 701,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n\nvar winston = require('../config/winston');\n\nvar Pend"
  },
  {
    "path": "models/permissionConstants.js",
    "chars": 582,
    "preview": "const OWNER_ROLE = [                                \n        ];\n\nconst ADMIN_ROLE = [                               \n   "
  },
  {
    "path": "models/presence.js",
    "chars": 366,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar Prese"
  },
  {
    "path": "models/profile.js",
    "chars": 2156,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nlet isComm"
  },
  {
    "path": "models/project.js",
    "chars": 6778,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar Profil"
  },
  {
    "path": "models/project_user.js",
    "chars": 5946,
    "preview": "'use strict';\n\nvar mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winsto"
  },
  {
    "path": "models/projectf.js",
    "chars": 3182,
    "preview": "var fs = require('fs');\n\nvar util = require('util');\nvar mongoose = require('mongoose');\n\n\nconst methods = [ 'add',\n    "
  },
  {
    "path": "models/property.js",
    "chars": 820,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar Prope"
  },
  {
    "path": "models/request.js",
    "chars": 17652,
    "preview": "var mongoose = require('mongoose');\n// mongoose.set(\"debug\", true);\nvar Schema = mongoose.Schema;\nvar winston = require("
  },
  {
    "path": "models/requestConstants.js",
    "chars": 157,
    "preview": "module.exports = {\n        TEMP : 50,\n        UNASSIGNED : 100,\n        ABANDONED: 150,\n        ASSIGNED : 200,\n        "
  },
  {
    "path": "models/requestSnapshot.js",
    "chars": 744,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar Projec"
  },
  {
    "path": "models/requestStatus.js",
    "chars": 384,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar Requ"
  },
  {
    "path": "models/requester.js",
    "chars": 503,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar Requ"
  },
  {
    "path": "models/role.js",
    "chars": 557,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar Role"
  },
  {
    "path": "models/roleConstants.js",
    "chars": 328,
    "preview": "module.exports = {\n        TYPE_AGENTS : 1,\n        TYPE_USERS : 2,\n        // SUPERADMIN : 'superadmin',\n        OWNER "
  },
  {
    "path": "models/routerLogger.js",
    "chars": 1343,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar config = require('../config/database');\n\n\nvar wins"
  },
  {
    "path": "models/routingConstants.js",
    "chars": 79,
    "preview": "module.exports = {\n        POOLED : 'pooled',\n        ASSIGNED : 'assigned',\n}\n"
  },
  {
    "path": "models/segment.js",
    "chars": 1151,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\n\nvar Seg"
  },
  {
    "path": "models/setting.js",
    "chars": 821,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar config"
  },
  {
    "path": "models/subscription.js",
    "chars": 1132,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nconst uuidv4 = require('uuid/v4');\n\nvar winston = requ"
  },
  {
    "path": "models/subscriptionLog.js",
    "chars": 541,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n\n\n\nvar SubscriptionLogSchema = new Schema({\n  event: {"
  },
  {
    "path": "models/tag.js",
    "chars": 361,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar TagSc"
  },
  {
    "path": "models/tagLibrary.js",
    "chars": 689,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar TagSchema = require('./tag');\n\n\n\nvar TagLibrarySch"
  },
  {
    "path": "models/transaction.js",
    "chars": 601,
    "preview": "const mongoose = require('mongoose');\n\nconst TransactionSchema = mongoose.Schema({\n  transaction_id: {\n    type: String,"
  },
  {
    "path": "models/user.js",
    "chars": 3408,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar bcrypt = require('bcrypt-nodejs');\nvar winston = r"
  },
  {
    "path": "models/webhook.js",
    "chars": 1437,
    "preview": "const mongoose = require('mongoose');\nconst { customAlphabet } = require('nanoid');\nconst nanoid = customAlphabet('abcde"
  },
  {
    "path": "models/whatsappLog.js",
    "chars": 631,
    "preview": "const mongoose = require('mongoose');\n\nconst MessageLogSchema = mongoose.Schema({\n  id_project: {\n    type: String,\n    "
  },
  {
    "path": "package.json",
    "chars": 4909,
    "preview": "{\n  \"name\": \"@tiledesk/tiledesk-server\",\n  \"description\": \"The Tiledesk server module\",\n  \"version\": \"2.18.3\",\n  \"script"
  },
  {
    "path": "public/loaderio-e6d5fbf72a45848fe2f43f7a986a1103.txt",
    "chars": 41,
    "preview": "loaderio-e6d5fbf72a45848fe2f43f7a986a1103"
  },
  {
    "path": "public/samples/bot/external/searcher/parse",
    "chars": 312,
    "preview": "{\n    \"text\": \"question\",\n    \"intent\": { \"name\": \"brutteparole\", \"confidence\": 0.9257488250732422 },\n    \"entities\": []"
  },
  {
    "path": "public/stylesheets/style.css",
    "chars": 111,
    "preview": "body {\n  padding: 50px;\n  font: 14px \"Lucida Grande\", Helvetica, Arial, sans-serif;\n}\n\na {\n  color: #00B7FF;\n}\n"
  },
  {
    "path": "public/wstest/TB.js",
    "chars": 420,
    "preview": "// https://www.dofactory.com/javascript/singleton-design-pattern\nvar Singleton = (function () {\n    var instance;\n \n    "
  },
  {
    "path": "public/wstest/index.html",
    "chars": 2389,
    "preview": "<html>\n    <head>\n            <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script>\n "
  },
  {
    "path": "public/wstest/tilebase.js",
    "chars": 5725,
    "preview": "class Tilebase {\n\n\n    constructor(url, onCreate, onUpdate) {\n      this.url = url;\n      this.onCreate = onCreate;\n    "
  },
  {
    "path": "publiccode.yml",
    "chars": 3180,
    "preview": "# This repository adheres to the publiccode.yml standard by including this \n# metadata file that makes public software e"
  },
  {
    "path": "pubmodules/activities/activityArchiver.js",
    "chars": 21672,
    "preview": "const authEvent = require('../../event/authEvent');\nconst requestEvent = require('../../event/requestEvent');\nvar Activi"
  },
  {
    "path": "pubmodules/activities/index.js",
    "chars": 189,
    "preview": "const activityRoute = require(\"./routes/activity\");\nconst activityArchiver = require(\"./activityArchiver\");\nmodule.expor"
  },
  {
    "path": "pubmodules/activities/models/activity.js",
    "chars": 2466,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n\n// https://getstream.io/blog/designing-activity-strea"
  },
  {
    "path": "pubmodules/activities/routes/activity.js",
    "chars": 20796,
    "preview": "var express = require('express');\nvar router = express.Router();\nvar Activity = require(\"../models/activity\");\nvar winst"
  },
  {
    "path": "pubmodules/activities/test/activityRoute.js",
    "chars": 2545,
    "preview": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar Activity = require('../models/acti"
  },
  {
    "path": "pubmodules/analytics/analytics.js",
    "chars": 52591,
    "preview": "var express = require('express');\nvar router = express.Router();\nvar AnalyticResult = require(\"../../models/analyticResu"
  },
  {
    "path": "pubmodules/analytics/index.js",
    "chars": 97,
    "preview": "const analyticsRoute = require(\"./analytics\");\n\nmodule.exports = {analyticsRoute:analyticsRoute};"
  },
  {
    "path": "pubmodules/apps/index.js",
    "chars": 186,
    "preview": "const listener = require(\"./listener\");\n\nconst apps = require(\"@tiledesk/tiledesk-apps\");\nconst appsRoute = apps.router;"
  },
  {
    "path": "pubmodules/apps/listener.js",
    "chars": 1370,
    "preview": "const apps = require(\"@tiledesk/tiledesk-apps\");\nvar winston = require('../../config/winston');\n\nvar config = require('."
  },
  {
    "path": "pubmodules/cache/index.js",
    "chars": 113,
    "preview": "const mongooseCachegoose = require(\"./mongoose-cachegoose-fn\");\nmodule.exports = {cachegoose:mongooseCachegoose};"
  },
  {
    "path": "pubmodules/cache/mongoose-cachegoose-fn.js",
    "chars": 42947,
    "preview": " var requestEvent = require(\"../../event/requestEvent\");   \n var messageEvent = require(\"../../event/messageEvent\");   \n"
  },
  {
    "path": "pubmodules/canned/cannedResponse.js",
    "chars": 1023,
    "preview": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../../config/winston');\n\n\nvar C"
  },
  {
    "path": "pubmodules/canned/cannedResponseRoute.js",
    "chars": 11545,
    "preview": "var express = require('express');\nvar router = express.Router();\nvar CannedResponse = require(\"./cannedResponse\");\nvar w"
  },
  {
    "path": "pubmodules/canned/index.js",
    "chars": 123,
    "preview": "const cannedResponseRoute = require(\"./cannedResponseRoute\");\n\nmodule.exports = {cannedResponseRoute: cannedResponseRout"
  },
  {
    "path": "pubmodules/chatbotTemplates/index.js",
    "chars": 222,
    "preview": "const listener = require(\"./listener\");\n\nconst templates = require(\"@tiledesk/tiledesk-chatbot-templates\");\nconst templa"
  }
]

// ... and 306 more files (download for full content)

About this extraction

This page contains the full source code of the Tiledesk/tiledesk-server GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 506 files (3.6 MB), approximately 975.3k tokens, and a symbol index with 810 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!