Beispiel #1
0
 public void JsonTestMessageCard()
 {
     MessageCard card = new MessageCard()
     {
         Title    = "Test",
         Summary  = "Summary",
         Sections = new List <Section>()
         {
             new Section()
             {
                 Title = "SectionTitle",
                 Text  = "SectionText",
                 Facts = new List <MessageFact>()
                 {
                     new MessageFact("Fact1", "Value1234234"),
                     new MessageFact("Fact2", "Value2"),
                     new MessageFact("Fact3", "Value3")
                 }
             }
         }
     };
     var    json         = JsonSerializer.Serialize(card, TeamsHookSystemTextSerializationOptions.Instance);
     string expectedJson = "";
     //Assert.True(json == expectedJson);
 }
        private static void Process(Options options)
        {
            var card = new MessageCard()
            {
                Title      = ParseString(options.Title),
                Summary    = ParseString(options.Summary),
                Text       = ParseString(options.Text),
                ThemeColor = ParseString(options.ThemeColor),
                Sections   = ParseCollection <Section>(options.Sections),
                Actions    = ParseCollection <BaseAction>(options.Actions)
            };

            var converted  = JsonConvert.SerializeObject(card, settings);
            var message    = (string)null;
            var requestUri = options.WebhookUri;

            using (var client = new HttpClient())
                using (var content = new StringContent(converted, Encoding.UTF8, "application/json"))
                    using (var response = client.PostAsync(requestUri, content).Result)
                    {
                        try
                        {
                            response.EnsureSuccessStatusCode();

                            message = converted;
                        }
                        catch (HttpRequestException ex)
                        {
                            message = ex.Message;
                        }
                    }

            Console.WriteLine($"Message sent: {message}");
        }
Beispiel #3
0
        protected virtual void RenderHeader(ComicStrip comic, MessageCard card)
        {
            string             extraButtonsMarkdown = "";
            List <ExtraButton> inlineButtons        = comic.ExtraButtons.Where(x => x.Location == ExtraButtonLocation.HeaderInline).ToList();

            if (inlineButtons.Any())
            {
                foreach (ExtraButton inlineButton in inlineButtons)
                {
                    extraButtonsMarkdown += $" *|* {BuildLink(inlineButton.Text, inlineButton.Url)}";
                }
            }

            extraButtonsMarkdown += RenderSenderInfo();

            card.Blocks.Add(new Block()
            {
                Type     = Types.Context,
                Elements = new List <Text>()
                {
                    new Text()
                    {
                        Type     = Types.Markdown,
                        TextText = $"{BuildLink($"See on {GetDomain(comic)} :arrow_upper_right:", comic.PageUrl)} *|* " + GetNavigationButtons(comic) + extraButtonsMarkdown
                    }
                }
            });
        }
Beispiel #4
0
        private void NewDatabase(object sender, RoutedEventArgs e)
        {
            vieModel_StartUp.CurrentSearch = "";
            vieModel_StartUp.Sort          = true;
            vieModel_StartUp.SortType      = "创建时间";
            DialogInput input = new DialogInput(this, Jvedio.Language.Resources.NewLibrary);

            if (input.ShowDialog() == false)
            {
                return;
            }
            string targetName = input.Text;

            if (string.IsNullOrEmpty(targetName) || targetName.IndexOfAny(System.IO.Path.GetInvalidFileNameChars()) != -1)
            {
                MessageCard.Show("名称非法!");
                return;
            }
            if (vieModel_StartUp.Databases.Where(x => x.Name == targetName).Any())
            {
                MessageCard.Show(Jvedio.Language.Resources.Message_AlreadyExist);
                return;
            }

            Jvedio.Core.Command.Sqlite.CreateVideoDataBase.Execute(targetName);
            vieModel_StartUp.ScanDatabase();
        }
        public async Task SendNotification(string title, string message, Dictionary <string, string> facts = null, string color = null)
        {
            if (!configuration.Enabled)
            {
                return;
            }

            using var client = new TeamsNotificationClient(configuration.Webhook);

            var messageCard = new MessageCard
            {
                Title = title,
                Text  = message,
                Color = color
            };

            if (facts != null)
            {
                messageCard.Sections = new List <MessageSection>()
                {
                    new MessageSection
                    {
                        Facts = facts.Select(t => new MessageFact
                        {
                            Name  = t.Key,
                            Value = t.Value
                        }).ToList()
                    }
                };
            }

            await client.PostMessage(messageCard);
        }
Beispiel #6
0
 private void ImportDatabase(object sender, RoutedEventArgs e)
 {
     System.Windows.Forms.OpenFileDialog OpenFileDialog1 = new System.Windows.Forms.OpenFileDialog();
     OpenFileDialog1.Title       = Jvedio.Language.Resources.ChooseDataBase;
     OpenFileDialog1.Filter      = $"Sqlite {Jvedio.Language.Resources.File}|*.sqlite";
     OpenFileDialog1.Multiselect = true;
     if (OpenFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
     {
         string[] names = OpenFileDialog1.FileNames;
         foreach (var item in names)
         {
             string name = Path.GetFileNameWithoutExtension(item);
             if (!DataBase.IsProperSqlite(item))
             {
                 MessageCard.Show("不支持该文件:" + item);
                 continue;
             }
             string targetPath = Path.Combine(GlobalVariable.DataPath, GlobalVariable.CurrentInfoType.ToString(), name + ".sqlite");
             if (File.Exists(targetPath))
             {
                 if (new Msgbox(this, $"{Jvedio.Language.Resources.Message_AlreadyExist} {name} {Jvedio.Language.Resources.IsToOverWrite} ?").ShowDialog() == true)
                 {
                     FileHelper.TryCopyFile(item, targetPath, true);
                     vieModel_StartUp.ScanDatabase();
                 }
             }
             else
             {
                 FileHelper.TryCopyFile(item, targetPath, true);
                 vieModel_StartUp.ScanDatabase();
             }
         }
     }
 }
Beispiel #7
0
        private void RenameSqlite(object sender, RoutedEventArgs e)
        {
            Core.pojo.data.SqliteInfo sqliteInfo = vieModel_StartUp.CurrentDatabases[listBox.SelectedIndex];
            string      originName = sqliteInfo.Name;
            string      originPath = sqliteInfo.Path;
            DialogInput input      = new DialogInput(this, Jvedio.Language.Resources.Rename, originName);

            if (input.ShowDialog() == false)
            {
                return;
            }
            string targetName = input.Text;

            if (targetName == originName)
            {
                return;
            }
            if (string.IsNullOrEmpty(targetName) || targetName.IndexOfAny(System.IO.Path.GetInvalidFileNameChars()) != -1)
            {
                MessageCard.Show("名称非法!");
                return;
            }
            string targetPath = Path.Combine(GlobalVariable.DataPath, GlobalVariable.CurrentInfoType.ToString(), targetName + ".sqlite");

            if (File.Exists(targetPath))
            {
                MessageCard.Show(Jvedio.Language.Resources.Message_AlreadyExist);
                return;
            }
            sqliteInfo.Name = targetName;
            sqliteInfo.Path = targetPath;
            FileHelper.TryMoveFile(originPath, targetPath);
            ConfigConnection.Instance.UpdateSqliteInfoPath(sqliteInfo);
            vieModel_StartUp.ScanDatabase(); // todo 仅更新重命名的
        }
        public void CanSerializeJSON()
        {
            // Arrange
            const string summary = "Summary";
            const string title   = "Title";
            const string text    = "Text";
            const string color   = "FF0000";
            var          sut     = new MessageCard(
                Summary: summary,
                ThemeColor: color,
                Title: title,
                Text: text,
                Sections: Array.Empty <MessageSection>()
                );

            // Act
            string json = JsonSerializer.Serialize(sut, JsonConfig.Default);

            // Assert
            json.Should().Contain(Type)
            .And.Contain(Context)
            .And.Contain("\"summary\":\"" + summary + "\"")
            .And.Contain("\"title\":\"" + title + "\"")
            .And.Contain("\"text\":\"" + text + "\"")
            .And.Contain("\"themeColor\":\"" + color + "\"")
            .And.Contain("\"sections\":[]")
            .And.NotContain("\"potentialActions\":");
        }
        public void CanDeserializeJson(string json, MessageCard expected)
        {
            // Arrange - Act
            var card = JsonSerializer.Deserialize <MessageCard>(json);

            // Assert
            card !.Title.Should().Be(expected.Title);
            card.Text.Should().Be(expected.Text);
            card.ThemeColor.Should().Be(expected.ThemeColor);

            if (expected.Sections is null)
            {
                card.Sections.Should().BeNull();
            }
            else
            {
                card.Sections.Should().HaveCount(expected.Sections.Count)
                .And.ContainInOrder(expected.Sections);
            }

            if (expected.PotentialActions is null)
            {
                card.PotentialActions.Should().BeNull();
            }
            else
            {
                card.PotentialActions.Should().HaveCount(expected.PotentialActions.Count)
                .And.ContainInOrder(expected.PotentialActions);
            }
        }
        private HttpContent GetContent()
        {
            MessageCard card = this.cardCreator.GetMessageCard(comic);
            string      json = JsonConvert.SerializeObject(card);

            return(new StringContent(json, Encoding.UTF8, "application/json"));
        }
Beispiel #11
0
        protected virtual void RenderTagsSection(ComicStrip comic, MessageCard card)
        {
            if (comic.Tags.Any())
            {
                card.Blocks.Add(new Block()
                {
                    Type = Types.Divider
                });
                card.Blocks.Add(new Block()
                {
                    Type     = Types.Context,
                    Elements = new List <Text>()
                    {
                        new Text()
                        {
                            Type     = Types.Markdown,
                            TextText = RenderTags(),
                        },
                    }
                });
            }
            string RenderTags()
            {
                List <string> tags = new List <string>();

                foreach (Tag tag in comic.Tags)
                {
                    tags.Add($"{BuildLink($"#{tag.Text}", tag.Url)}");
                }
                return(string.Join(", ", tags));
            }
        }
        /// <inheritdoc />
        public IMessageHandler BuildMessage(Options options, JsonSerializerSettings settings)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            if (settings == null)
            {
                throw new ArgumentNullException(nameof(settings));
            }

            var card = new MessageCard()
            {
                Title      = ParseString(options.Title),
                Summary    = ParseString(options.Summary),
                Text       = ParseString(options.Text),
                ThemeColor = ParseString(options.ThemeColor),
                Sections   = ParseCollection <Section>(options.Sections, settings),
                Actions    = ParseCollection <BaseAction>(options.Actions, settings)
            };

            this.Converted  = JsonConvert.SerializeObject(card, settings);
            this.RequestUri = options.WebhookUri;

            return(this);
        }
 public Form1()
 {
     InitializeComponent();
     _webHookDelivery             = new WebHookDelivery(_webHookUrl);
     _connectorPayload            = SetupConnector();
     propertyGrid1.SelectedObject = _connectorPayload;
 }
Beispiel #14
0
 protected virtual void RenderImageSection(ComicStrip comic, MessageCard card)
 {
     card.Sections.Add(new Section()
     {
         Markdown = true,
         Text     = $"![{comic.Title} (if comic is not visible, perhaps it's too 'large/heavy' for Teams, sorry)]({comic.ImageUrl})",
     });
 }
Beispiel #15
0
        static string GetMessageCard()
        {
            var card = new MessageCard()
            {
                type    = "MessageCard",
                context = "https://schema.org/extensions",
                summary = "Something Broke",
                title   = "Something is broken"
            };

            var osTarget = new Dictionary <string, string>();

            osTarget.Add("os", "default");
            osTarget.Add("uri", "https://www.google.com");
            var targets = new List <Dictionary <string, string> >();

            targets.Add(osTarget);

            var potentialAction = new Action()
            {
                type    = "OpenUri",
                name    = "Get Help",
                targets = targets
            };

            card.potentialAction = new List <Action>()
            {
                potentialAction
            };

            var section = new Section()
            {
                activitySubtitle = "Badness afoot",
                activityTitle    = "Warning!"
            };

            section.facts = new List <Fact>()
            {
                new Fact()
                {
                    name = "Incident Time", value = "Recently. **very** recently."
                },
                new Fact()
                {
                    name = "Suggested action", value = "[Be upset](http://nooooooooooooooo.com/)"
                },
                new Fact()
                {
                    name = "Value of some metric", value = "11"
                }
            };

            card.sections = new List <Section> {
                section
            };

            return(JsonConvert.SerializeObject(card));
        }
        static void notifyTeamsPlanner(Messages value)
        {
            List <MessageValue> val = value.value;

            //List<CardSection> cardSection = new List<CardSection>();
            MessageCard card = new MessageCard();


            foreach (var item in val)
            {
                List <Message> msg = new List <Message>();
                msg = item.Messages;

                List <CardFacts> facts = new List <CardFacts>();

                foreach (var ite in msg)
                {
                    CardFacts fa = new CardFacts()
                    {
                        name  = ite.PublishedTime.ToString(),
                        value = ite.MessageText
                    };
                    facts.Add(fa);
                }
                List <CardSection> cardSection = new List <CardSection>();

                CardSection cardS = new CardSection()
                {
                    facts            = facts,
                    text             = item.WorkloadDisplayName,
                    activityTitle    = item.FeatureDisplayName,
                    activitySubtitle = item.Id + " " + item.ImpactDescription
                };

                cardSection.Add(cardS);

                card = new MessageCard()
                {
                    sections = cardSection,
                    summary  = item.Status,
                    title    = item.Title
                };

                // My Teams LInk
                var client  = new RestClient(teamsWebhook);
                var request = new RestRequest(Method.POST);
                request.AddHeader("cache-control", "no-cache");
                request.AddHeader("Connection", "keep-alive");
                request.AddHeader("accept-encoding", "gzip, deflate");
                request.AddHeader("Host", "outlook.office.com");
                request.AddHeader("Cache-Control", "no-cache");
                request.AddHeader("Accept", "*/*");
                request.AddHeader("Content-Type", "application/json");
                request.AddParameter(JsonConvert.SerializeObject(card), ParameterType.RequestBody);
                IRestResponse response = client.Execute(request);
            }
        }
        /// <summary>
        /// Create a simple card message when there is no way to handle the log entry
        /// </summary>
        /// <param name="message">The message to post</param>
        /// <returns>A message card ready to post</returns>
        public static MessageCard CreateBasicMessage(string message)
        {
            MessageCard card = new MessageCard();

            card.Title = "Unknown log entry";
            card.Text  = message;

            return(card);
        }
        public MessageCard SignalPublisherTeams(ChannelRequest request, ILambdaContext ctx)
        {
            processor.Logger = new LambdaLogger(ctx.Logger);
            processor.Logger.Info($"Version : {Version}");
            processor.Logger.Info(JsonTools.Serialize(request));
            MessageCard reply = Teams.Publish(request);

            processor.Logger.Info(JsonTools.Serialize(reply));
            return(reply);
        }
Beispiel #19
0
 private static void RenderTitle(ComicStrip comic, MessageCard card)
 {
     card.Blocks.Add(new Block()
     {
         Type = Types.Header,
         Text = new Text()
         {
             TextText = $"{comic.Title}",
             Type     = Types.PlainText
         }
     });
 }
        private static MessageState GetCurrentState(MessageCard context,
                                                    MessageStates state,
                                                    bool isUpdatingState)
        {
            var stateName = Enum.GetName(typeof(MessageStates), state);
            var type      = Type.GetType($"PVBot.Clients.Portable.States.{stateName}");
            var newState  = (MessageState)Activator.CreateInstance(type);

            newState.EnterState(context, isUpdatingState);

            return(newState);
        }
Beispiel #21
0
        public virtual MessageCard GetMessageCard(ComicStrip comic)
        {
            MessageCard card = new MessageCard(comic.Title);

            RenderHeader(comic, card);

            RenderImageSection(comic, card);

            RenderTagsSection(comic, card);

            return(card);
        }
        public void Issue4_SerializeAllPropertiesOfDerivedActions()
        {
            var msgCard = new MessageCard
            {
                Title    = "Message Title (optional)",
                Text     = "This is a sing line **message** with markdown",
                Sections = new[]
                {
                    new Section
                    {
                        Text    = "Action Section",
                        Actions = new[]
                        {
                            new ActionCardAction
                            {
                                Name = "ActionCardAction",
                                Type = ActionType.ActionCard
                            },
                        }
                    },
                    new Section
                    {
                        Text    = "Action Section",
                        Actions = new[]
                        {
                            new HttpPostAction
                            {
                                Name = "HttpPostAction",
                                Type = ActionType.HttpPost,
                            },
                        }
                    },
                    new Section
                    {
                        Text    = "Action Section",
                        Actions = new[]
                        {
                            new OpenUriAction
                            {
                                Name    = "OpenUriAction",
                                Type    = ActionType.OpenUri,
                                Targets = new List <Target>()
                            },
                        }
                    },
                },
            };
            var json = msgCard.ToJson();

            Debug.WriteLine(json);
            Assert.IsTrue(json.Contains("inputs") && json.Contains("headers") && json.Contains("targets"));
        }
        private void EnterState(MessageCard context, bool isUpdatingState)
        {
            Context = context;

            if (isUpdatingState)
            {
                UpdateState(isUpdatingState);
            }
            else
            {
                SetState();
            }
        }
Beispiel #24
0
 protected virtual void RenderImageSection(ComicStrip comic, MessageCard card)
 {
     card.Blocks.Add(new Block()
     {
         Type  = Types.Image,
         Title = new Title()
         {
             Type = Types.PlainText,
             Text = comic.Date
         },
         ImageUrl = new Uri(comic.ImageUrl),
         AltText  = comic.Title
     });
 }
Beispiel #25
0
        public void Log <TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func <TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel))
            {
                return;
            }
            if (string.IsNullOrEmpty(_config.Channel))
            {
                return;
            }
            if (_config.EventId == 0 || _config.EventId == eventId.Id)
            {
                var httpClient = new HttpClient
                {
                    BaseAddress = new Uri(_config.Channel)
                };
                httpClient.DefaultRequestHeaders.Add("cache-control", "no-cache");
                httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                var message = new MessageCard
                {
                    ThemeColor = "0076D7",
                    Summary    = $"{_name} Logging",
                    Sections   = new[]
                    {
                        new Section
                        {
                            Facts = new[]
                            {
                                new Fact {
                                    Name = "Name", Value = $"{_name}"
                                },
                                new Fact {
                                    Name = "Event ID", Value = $"{eventId.Id}"
                                },
                                new Fact {
                                    Name = "Log Level", Value = $"{logLevel}"
                                },
                                new Fact {
                                    Name = "Message", Value = $"{formatter(state, exception)}"
                                }
                            }
                        }
                    }
                };
                var content  = new StringContent(message.ToJson(), Encoding.UTF8, "application/json");
                var response = httpClient.PostAsync(string.Empty, content).GetAwaiter().GetResult();

                response.EnsureSuccessStatusCode();
            }
        }
Beispiel #26
0
        internal async Task EnviarMensaje(SonarPayload analisis)
        {
            var estado     = analisis.status.ToLower();
            var taskStatus = estado.First().ToString().ToUpper() + estado.Substring(1);

            var mensaje = new MessageCard
            {
                Title    = $"Análisis {analisis.project.name}",
                Color    = analisis.qualityGate.status == "OK" ? Config.OkColor : Config.BadColor,
                Text     = $"Tarea: {analisis.taskId} - Estado {taskStatus}",
                Sections = new List <MessageSection>()
                {
                    new MessageSection()
                    {
                        Title    = analisis.qualityGate.status,
                        Subtitle = $"{analisis.analysedAt}",
                        Image    = analisis.qualityGate.status == "OK" ? Config.OkIcon : Config.BadIcon,
                        Facts    = new List <MessageFact>()
                    }
                }
            };

            foreach (Condition condicion in analisis.qualityGate.conditions)
            {
                mensaje.Sections[0].Facts.Add(new MessageFact()
                {
                    Name = condicion.metric, Value = condicion.status
                });
            }

            mensaje.PotentialActions = new List <PotentialAction>
            {
                new PotentialAction()
                {
                    Name    = "Abrir en Sonar",
                    Targets = new List <PotentialActionLink>()
                    {
                        new PotentialActionLink()
                        {
                            Value = analisis.project.url
                        }
                    }
                }
            };

            var json     = JsonConvert.SerializeObject(mensaje);
            var response = await client.PostAsync(uri, new StringContent(json, Encoding.UTF8, "application/json")).ConfigureAwait(false);

            response.EnsureSuccessStatusCode();
        }
        private async Task SendCommentNotification(string title, string body, string teamName, string commentUri, string username, DateTimeOffset date)
        {
            try
            {
                _logger.LogInformation("Sending notification about mention of {teamName} at {uri}.", teamName, commentUri);
                var message = new MessageCard
                {
                    ThemeColor = "0072C6",
                    Text       = $"Team {teamName} was mentioned in an issue.",
                    Actions    = new List <IAction>
                    {
                        new OpenUri
                        {
                            Name    = "Open comment",
                            Targets = new List <Target>
                            {
                                new Target
                                {
                                    OperatingSystem = "default",
                                    Uri             = commentUri
                                }
                            }
                        }
                    },
                    Sections = new List <Section>
                    {
                        new Section
                        {
                            ActivityTitle    = $"{username}",
                            ActivitySubtitle = $"on {date:g}",
                            ActivityText     = body
                        }
                    }
                };
                using HttpClient client = _httpClientFactory.CreateClient();
                using var req           = new HttpRequestMessage(HttpMethod.Post, _options.Value.TeamsWebHookUri)
                      {
                          Content = new StringContent(JsonConvert.SerializeObject(message), Encoding.UTF8),
                      };
                using HttpResponseMessage res = await client.SendAsync(req);

                res.EnsureSuccessStatusCode();
                _logger.LogInformation("Sent notification about mention of {teamName} at {uri}.", teamName, commentUri);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unable to send notification about mention of {teamName} at {uri}.", teamName, commentUri);
            }
        }
Beispiel #28
0
        public async Task ProcessAsync(MessageCard card)
        {
            var requestUri = Settings.GetSetting("MSTeamsPublishing.TeamsWebhookUrl", string.Empty);
            var converted  = card.ToJson();

            converted = converted.Replace("Default", "default");
            converted = converted.Replace("\"@type\":2", "\"@type\":\"OpenUri\"");

            using (var client = new HttpClient())
                using (var content = new StringContent(converted, Encoding.UTF8, "application/json"))
                    using (var response = await client.PostAsync(requestUri, content).ConfigureAwait(false))
                    {
                        response.EnsureSuccessStatusCode();
                    }
        }
        /// <summary>
        /// Create a message card ready to publish in Teams
        /// TODO: rather than string, need to create a class or have multiple parameters to the message to post
        /// </summary>
        /// <param name="message">The message to post see TODO</param>
        /// <returns>A message card ready to post</returns>
        public static MessageCard CreateMessageCard(string message, JsonLogEntry logEntry)
        {
            MessageCard card = new MessageCard();

            card.Title = "Log entry error";
            card.Text  = $"Error: {message}";

            card.Sections = new List <Section>
            {
                new Section
                {
                    ActivityTitle    = logEntry?.Trigram,
                    ActivitySubtitle = DateTime.Now.ToString(),
                    Facts            = new List <Fact>
                    {
                        new Fact {
                            Name = $"{nameof(logEntry.Date)}", Value = logEntry?.Date
                        },
                        new Fact {
                            Name = $"{nameof(logEntry.Level)}", Value = logEntry?.Level
                        }
                    },
                    Text = $"Original message: {logEntry?.Message}"
                }
            };

            card.Actions = new List <IAction>
            {
                new OpenUriAction
                {
                    Type    = ActionType.OpenUri,
                    Name    = "View on site",
                    Targets = new List <Target> {
                        new Target {
                            OS = TargetOs.Default, Uri = string.Format(Environment.GetEnvironmentVariable(DefaultKibanaUrl), logEntry?.Trigram)
                        }
                    }
                }
            };

            return(card);
        }
Beispiel #30
0
        protected virtual void RenderTagsSection(ComicStrip comic, MessageCard card)
        {
            if (comic.Tags.Any())
            {
                card.Sections.Add(new Section()
                {
                    Markdown = true,
                    Text     = RenderTags(),
                });
            }
            string RenderTags()
            {
                List <string> tags = new List <string>();

                foreach (Tag tag in comic.Tags)
                {
                    tags.Add($"{BuildLink($"#{tag.Text}", tag.Url)}");
                }
                return(string.Join(", ", tags));
            }
        }