├── .gitattributes ├── .gitignore ├── GitVersion.yml ├── HashBus.sln ├── HashBus.sln.DotSettings ├── LICENSE ├── README.md ├── nuget.config └── src ├── HashBus.NServiceBusConfiguration ├── BusConfigurationExtensions.cs └── HashBus.NServiceBusConfiguration.csproj ├── HashBus.Projector.MostHashtagged ├── App.cs ├── HashBus.Projector.MostHashtagged.csproj ├── MostHashtaggedProjection.cs ├── Program.cs └── app.config ├── HashBus.Projector.MostMentioned ├── App.cs ├── HashBus.Projector.MostMentioned.csproj ├── MostMentionedProjection.cs ├── Program.cs └── app.config ├── HashBus.Projector.MostRetweeted ├── App.cs ├── HashBus.Projector.MostRetweeted.csproj ├── MostRetweetedProjection.cs ├── Program.cs └── app.config ├── HashBus.Projector.TopRetweeters ├── App.cs ├── HashBus.Projector.TopRetweeters.csproj ├── Program.cs ├── TopRetweetersProjection.cs └── app.config ├── HashBus.Projector.TopTweeters ├── App.cs ├── HashBus.Projector.TopTweeters.csproj ├── Program.cs ├── TopTweetersProjection.cs └── app.config ├── HashBus.Projector.TopTweetersRetweeters ├── App.cs ├── HashBus.Projector.TopTweetersRetweeters.csproj ├── Program.cs ├── TopTweetersRetweetersProjection.cs └── app.config ├── HashBus.ReadModel.MongoDB ├── HashBus.ReadModel.MongoDB.csproj └── MongoDBListRepository.cs ├── HashBus.ReadModel ├── HashBus.ReadModel.csproj ├── Hashtag.cs ├── IRepository.cs ├── Mention.cs ├── Retweet.cs ├── Retweetee.cs ├── Tweet.cs └── TweetRetweet.cs ├── HashBus.Twitter.Analyzer.Commands ├── AnalyzeTweet.cs ├── HashBus.Twitter.Analyzer.Commands.csproj ├── Hashtag.cs ├── Tweet.cs └── UserMention.cs ├── HashBus.Twitter.Analyzer.Events ├── HashBus.Twitter.Analyzer.Events.csproj ├── Hashtag.cs ├── Tweet.cs ├── TweetAnalyzed.cs └── UserMention.cs ├── HashBus.Twitter.Analyzer ├── AnalyzeTweetHandler.cs ├── App.cs ├── HashBus.Twitter.Analyzer.csproj ├── Program.cs ├── TweetMapper.cs └── app.config ├── HashBus.Twitter.BackFill ├── App.cs ├── HashBus.Twitter.BackFill.csproj ├── Program.cs └── app.config ├── HashBus.Twitter.CatchUp.Commands ├── HashBus.Twitter.CatchUp.Commands.csproj └── StartCatchUp.cs ├── HashBus.Twitter.CatchUp ├── App.cs ├── HashBus.Twitter.CatchUp.csproj ├── ITweetService.cs ├── Program.cs ├── StartCatchUpHandler.cs ├── TweetReceivedSaga.cs ├── TweetReceivedSagaData.cs ├── TweetReceivedSagaFinder.cs ├── TweetService.cs └── app.config ├── HashBus.Twitter.Monitor.Events ├── HashBus.Twitter.Monitor.Events.csproj └── TweetReceived.cs ├── HashBus.Twitter.Monitor.Simulator ├── App.cs ├── HashBus.Twitter.Monitor.Simulator.csproj ├── Program.cs ├── Simulation.cs └── app.config ├── HashBus.Twitter.Monitor ├── App.cs ├── HashBus.Twitter.Monitor.csproj ├── Monitoring.cs ├── Program.cs ├── TweetMapper.cs ├── Writer.cs └── app.config ├── HashBus.Viewer ├── App.cs ├── ColorTokenExtensions.cs ├── ConsoleHelper.cs ├── HashBus.Viewer.csproj ├── IRunAsync.cs ├── IService.cs ├── LeaderboardService.cs ├── LeaderboardView.cs ├── MostHashtaggedLeaderBoardViewFactory.cs ├── MostMentionedLeaderBoardViewFactory.cs ├── MostRetweetedLeaderBoardViewFactory.cs ├── Program.cs ├── TopRetweetersLeaderBoardViewFactory.cs ├── TopTweetersLeaderBoardViewFactory.cs ├── TopTweetersRetweetersLeaderBoardViewFactory.cs └── app.config └── HashBus.WebApi ├── App.cs ├── HashBus.WebApi.csproj ├── HashtagEntry.cs ├── IEntry.cs ├── IIgnoredHashtagsService.cs ├── IIgnoredUserNamesService.cs ├── IgnoredHashtagsService.cs ├── IgnoredUserNamesService.cs ├── Leaderboard.cs ├── MostHashtaggedModule.cs ├── MostMentionedModule.cs ├── MostRetweetedModule.cs ├── Program.cs ├── TopRetweetersModule.cs ├── TopTweetersModule.cs ├── TopTweetersRetweetersModule.cs ├── UserEntry.cs └── app.config /.gitattributes: -------------------------------------------------------------------------------- 1 | * -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | bin/ 3 | obj/ 4 | packages/ 5 | 6 | *.suo 7 | *.user 8 | -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | assembly-versioning-scheme: Major 2 | next-version: 5 3 | -------------------------------------------------------------------------------- /HashBus.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2010 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Twitter.Monitor.Events", "src\HashBus.Twitter.Monitor.Events\HashBus.Twitter.Monitor.Events.csproj", "{5728DE02-C264-4697-B554-DDA5FAD10A47}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Twitter.Monitor.Simulator", "src\HashBus.Twitter.Monitor.Simulator\HashBus.Twitter.Monitor.Simulator.csproj", "{1BEFE50E-24CA-4CAA-8B2C-7F8CD1771CB5}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Twitter.Analyzer", "src\HashBus.Twitter.Analyzer\HashBus.Twitter.Analyzer.csproj", "{EC04FD05-F3FE-439A-8791-D85856E3726A}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Twitter.Monitor", "src\HashBus.Twitter.Monitor\HashBus.Twitter.Monitor.csproj", "{B1453AE4-B343-42F0-B6F6-E9016A62B3C2}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B34C7F0A-2903-47F2-A087-06D3136C9A1F}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B77C5EBC-7265-4E5E-B22A-5928FAFDC674}" 17 | ProjectSection(SolutionItems) = preProject 18 | .gitattributes = .gitattributes 19 | .gitignore = .gitignore 20 | GitVersion.yml = GitVersion.yml 21 | HashBus.sln.DotSettings = HashBus.sln.DotSettings 22 | LICENSE = LICENSE 23 | nuget.config = nuget.config 24 | README.md = README.md 25 | EndProjectSection 26 | EndProject 27 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Twitter.Analyzer.Events", "src\HashBus.Twitter.Analyzer.Events\HashBus.Twitter.Analyzer.Events.csproj", "{6991A5CF-25CB-4336-9F6D-2EB1E01EFB75}" 28 | EndProject 29 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Projector.MostMentioned", "src\HashBus.Projector.MostMentioned\HashBus.Projector.MostMentioned.csproj", "{718D4969-A6F7-4B0B-8FD2-DE13F23F5860}" 30 | EndProject 31 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.ReadModel", "src\HashBus.ReadModel\HashBus.ReadModel.csproj", "{1709F0A9-F797-44FD-94C0-18A0D75442F8}" 32 | EndProject 33 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.ReadModel.MongoDB", "src\HashBus.ReadModel.MongoDB\HashBus.ReadModel.MongoDB.csproj", "{34C86079-CCAB-4312-98FC-F64F4A97D3A4}" 34 | EndProject 35 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Projector.TopTweeters", "src\HashBus.Projector.TopTweeters\HashBus.Projector.TopTweeters.csproj", "{501F4158-CF10-4C25-AAA3-7BE827924926}" 36 | EndProject 37 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.WebApi", "src\HashBus.WebApi\HashBus.WebApi.csproj", "{4EC0F237-1CF1-4A0F-B849-E8B75A18CEEB}" 38 | EndProject 39 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.NServiceBusConfiguration", "src\HashBus.NServiceBusConfiguration\HashBus.NServiceBusConfiguration.csproj", "{F302F223-C0CD-46A6-8BB6-01F1213BDD58}" 40 | EndProject 41 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Twitter.CatchUp", "src\HashBus.Twitter.CatchUp\HashBus.Twitter.CatchUp.csproj", "{63B64572-B72C-4BCE-BB3D-C3F7635A6858}" 42 | EndProject 43 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Twitter.CatchUp.Commands", "src\HashBus.Twitter.CatchUp.Commands\HashBus.Twitter.CatchUp.Commands.csproj", "{89C81FA0-A06B-4FB9-A3FC-4DAB2A93AA2C}" 44 | EndProject 45 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Projector.TopTweetersRetweeters", "src\HashBus.Projector.TopTweetersRetweeters\HashBus.Projector.TopTweetersRetweeters.csproj", "{BAD64799-5E68-4EA5-B117-1F0B3582392B}" 46 | EndProject 47 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Projector.TopRetweeters", "src\HashBus.Projector.TopRetweeters\HashBus.Projector.TopRetweeters.csproj", "{82A07696-48E6-4EA7-9DF6-3422157E861F}" 48 | EndProject 49 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Projector.MostRetweeted", "src\HashBus.Projector.MostRetweeted\HashBus.Projector.MostRetweeted.csproj", "{C345A6AA-A562-447F-8A2B-63645B4FE56F}" 50 | EndProject 51 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Projector.MostHashtagged", "src\HashBus.Projector.MostHashtagged\HashBus.Projector.MostHashtagged.csproj", "{ADADC124-AF03-428D-ADD5-9CB2DF88E81C}" 52 | EndProject 53 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Twitter.BackFill", "src\HashBus.Twitter.BackFill\HashBus.Twitter.BackFill.csproj", "{F36FD7F7-0C41-44E3-89FE-AAF560BA69DC}" 54 | EndProject 55 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Twitter.Analyzer.Commands", "src\HashBus.Twitter.Analyzer.Commands\HashBus.Twitter.Analyzer.Commands.csproj", "{293F96F9-7E15-4969-B8ED-595D06FD7AA5}" 56 | EndProject 57 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBus.Viewer", "src\HashBus.Viewer\HashBus.Viewer.csproj", "{8E09CB51-03C5-413A-8570-1A121A01B0A4}" 58 | EndProject 59 | Global 60 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 61 | Debug|Any CPU = Debug|Any CPU 62 | Release|Any CPU = Release|Any CPU 63 | EndGlobalSection 64 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 65 | {5728DE02-C264-4697-B554-DDA5FAD10A47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 66 | {5728DE02-C264-4697-B554-DDA5FAD10A47}.Debug|Any CPU.Build.0 = Debug|Any CPU 67 | {5728DE02-C264-4697-B554-DDA5FAD10A47}.Release|Any CPU.ActiveCfg = Release|Any CPU 68 | {5728DE02-C264-4697-B554-DDA5FAD10A47}.Release|Any CPU.Build.0 = Release|Any CPU 69 | {1BEFE50E-24CA-4CAA-8B2C-7F8CD1771CB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 70 | {1BEFE50E-24CA-4CAA-8B2C-7F8CD1771CB5}.Debug|Any CPU.Build.0 = Debug|Any CPU 71 | {1BEFE50E-24CA-4CAA-8B2C-7F8CD1771CB5}.Release|Any CPU.ActiveCfg = Release|Any CPU 72 | {1BEFE50E-24CA-4CAA-8B2C-7F8CD1771CB5}.Release|Any CPU.Build.0 = Release|Any CPU 73 | {EC04FD05-F3FE-439A-8791-D85856E3726A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 74 | {EC04FD05-F3FE-439A-8791-D85856E3726A}.Debug|Any CPU.Build.0 = Debug|Any CPU 75 | {EC04FD05-F3FE-439A-8791-D85856E3726A}.Release|Any CPU.ActiveCfg = Release|Any CPU 76 | {EC04FD05-F3FE-439A-8791-D85856E3726A}.Release|Any CPU.Build.0 = Release|Any CPU 77 | {B1453AE4-B343-42F0-B6F6-E9016A62B3C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 78 | {B1453AE4-B343-42F0-B6F6-E9016A62B3C2}.Debug|Any CPU.Build.0 = Debug|Any CPU 79 | {B1453AE4-B343-42F0-B6F6-E9016A62B3C2}.Release|Any CPU.ActiveCfg = Release|Any CPU 80 | {B1453AE4-B343-42F0-B6F6-E9016A62B3C2}.Release|Any CPU.Build.0 = Release|Any CPU 81 | {6991A5CF-25CB-4336-9F6D-2EB1E01EFB75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 82 | {6991A5CF-25CB-4336-9F6D-2EB1E01EFB75}.Debug|Any CPU.Build.0 = Debug|Any CPU 83 | {6991A5CF-25CB-4336-9F6D-2EB1E01EFB75}.Release|Any CPU.ActiveCfg = Release|Any CPU 84 | {6991A5CF-25CB-4336-9F6D-2EB1E01EFB75}.Release|Any CPU.Build.0 = Release|Any CPU 85 | {718D4969-A6F7-4B0B-8FD2-DE13F23F5860}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 86 | {718D4969-A6F7-4B0B-8FD2-DE13F23F5860}.Debug|Any CPU.Build.0 = Debug|Any CPU 87 | {718D4969-A6F7-4B0B-8FD2-DE13F23F5860}.Release|Any CPU.ActiveCfg = Release|Any CPU 88 | {718D4969-A6F7-4B0B-8FD2-DE13F23F5860}.Release|Any CPU.Build.0 = Release|Any CPU 89 | {1709F0A9-F797-44FD-94C0-18A0D75442F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 90 | {1709F0A9-F797-44FD-94C0-18A0D75442F8}.Debug|Any CPU.Build.0 = Debug|Any CPU 91 | {1709F0A9-F797-44FD-94C0-18A0D75442F8}.Release|Any CPU.ActiveCfg = Release|Any CPU 92 | {1709F0A9-F797-44FD-94C0-18A0D75442F8}.Release|Any CPU.Build.0 = Release|Any CPU 93 | {34C86079-CCAB-4312-98FC-F64F4A97D3A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 94 | {34C86079-CCAB-4312-98FC-F64F4A97D3A4}.Debug|Any CPU.Build.0 = Debug|Any CPU 95 | {34C86079-CCAB-4312-98FC-F64F4A97D3A4}.Release|Any CPU.ActiveCfg = Release|Any CPU 96 | {34C86079-CCAB-4312-98FC-F64F4A97D3A4}.Release|Any CPU.Build.0 = Release|Any CPU 97 | {501F4158-CF10-4C25-AAA3-7BE827924926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 98 | {501F4158-CF10-4C25-AAA3-7BE827924926}.Debug|Any CPU.Build.0 = Debug|Any CPU 99 | {501F4158-CF10-4C25-AAA3-7BE827924926}.Release|Any CPU.ActiveCfg = Release|Any CPU 100 | {501F4158-CF10-4C25-AAA3-7BE827924926}.Release|Any CPU.Build.0 = Release|Any CPU 101 | {4EC0F237-1CF1-4A0F-B849-E8B75A18CEEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 102 | {4EC0F237-1CF1-4A0F-B849-E8B75A18CEEB}.Debug|Any CPU.Build.0 = Debug|Any CPU 103 | {4EC0F237-1CF1-4A0F-B849-E8B75A18CEEB}.Release|Any CPU.ActiveCfg = Release|Any CPU 104 | {4EC0F237-1CF1-4A0F-B849-E8B75A18CEEB}.Release|Any CPU.Build.0 = Release|Any CPU 105 | {F302F223-C0CD-46A6-8BB6-01F1213BDD58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 106 | {F302F223-C0CD-46A6-8BB6-01F1213BDD58}.Debug|Any CPU.Build.0 = Debug|Any CPU 107 | {F302F223-C0CD-46A6-8BB6-01F1213BDD58}.Release|Any CPU.ActiveCfg = Release|Any CPU 108 | {F302F223-C0CD-46A6-8BB6-01F1213BDD58}.Release|Any CPU.Build.0 = Release|Any CPU 109 | {63B64572-B72C-4BCE-BB3D-C3F7635A6858}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 110 | {63B64572-B72C-4BCE-BB3D-C3F7635A6858}.Debug|Any CPU.Build.0 = Debug|Any CPU 111 | {63B64572-B72C-4BCE-BB3D-C3F7635A6858}.Release|Any CPU.ActiveCfg = Release|Any CPU 112 | {63B64572-B72C-4BCE-BB3D-C3F7635A6858}.Release|Any CPU.Build.0 = Release|Any CPU 113 | {89C81FA0-A06B-4FB9-A3FC-4DAB2A93AA2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 114 | {89C81FA0-A06B-4FB9-A3FC-4DAB2A93AA2C}.Debug|Any CPU.Build.0 = Debug|Any CPU 115 | {89C81FA0-A06B-4FB9-A3FC-4DAB2A93AA2C}.Release|Any CPU.ActiveCfg = Release|Any CPU 116 | {89C81FA0-A06B-4FB9-A3FC-4DAB2A93AA2C}.Release|Any CPU.Build.0 = Release|Any CPU 117 | {BAD64799-5E68-4EA5-B117-1F0B3582392B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 118 | {BAD64799-5E68-4EA5-B117-1F0B3582392B}.Debug|Any CPU.Build.0 = Debug|Any CPU 119 | {BAD64799-5E68-4EA5-B117-1F0B3582392B}.Release|Any CPU.ActiveCfg = Release|Any CPU 120 | {BAD64799-5E68-4EA5-B117-1F0B3582392B}.Release|Any CPU.Build.0 = Release|Any CPU 121 | {82A07696-48E6-4EA7-9DF6-3422157E861F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 122 | {82A07696-48E6-4EA7-9DF6-3422157E861F}.Debug|Any CPU.Build.0 = Debug|Any CPU 123 | {82A07696-48E6-4EA7-9DF6-3422157E861F}.Release|Any CPU.ActiveCfg = Release|Any CPU 124 | {82A07696-48E6-4EA7-9DF6-3422157E861F}.Release|Any CPU.Build.0 = Release|Any CPU 125 | {C345A6AA-A562-447F-8A2B-63645B4FE56F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 126 | {C345A6AA-A562-447F-8A2B-63645B4FE56F}.Debug|Any CPU.Build.0 = Debug|Any CPU 127 | {C345A6AA-A562-447F-8A2B-63645B4FE56F}.Release|Any CPU.ActiveCfg = Release|Any CPU 128 | {C345A6AA-A562-447F-8A2B-63645B4FE56F}.Release|Any CPU.Build.0 = Release|Any CPU 129 | {ADADC124-AF03-428D-ADD5-9CB2DF88E81C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 130 | {ADADC124-AF03-428D-ADD5-9CB2DF88E81C}.Debug|Any CPU.Build.0 = Debug|Any CPU 131 | {ADADC124-AF03-428D-ADD5-9CB2DF88E81C}.Release|Any CPU.ActiveCfg = Release|Any CPU 132 | {ADADC124-AF03-428D-ADD5-9CB2DF88E81C}.Release|Any CPU.Build.0 = Release|Any CPU 133 | {F36FD7F7-0C41-44E3-89FE-AAF560BA69DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 134 | {F36FD7F7-0C41-44E3-89FE-AAF560BA69DC}.Debug|Any CPU.Build.0 = Debug|Any CPU 135 | {F36FD7F7-0C41-44E3-89FE-AAF560BA69DC}.Release|Any CPU.ActiveCfg = Release|Any CPU 136 | {F36FD7F7-0C41-44E3-89FE-AAF560BA69DC}.Release|Any CPU.Build.0 = Release|Any CPU 137 | {293F96F9-7E15-4969-B8ED-595D06FD7AA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 138 | {293F96F9-7E15-4969-B8ED-595D06FD7AA5}.Debug|Any CPU.Build.0 = Debug|Any CPU 139 | {293F96F9-7E15-4969-B8ED-595D06FD7AA5}.Release|Any CPU.ActiveCfg = Release|Any CPU 140 | {293F96F9-7E15-4969-B8ED-595D06FD7AA5}.Release|Any CPU.Build.0 = Release|Any CPU 141 | {8E09CB51-03C5-413A-8570-1A121A01B0A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 142 | {8E09CB51-03C5-413A-8570-1A121A01B0A4}.Debug|Any CPU.Build.0 = Debug|Any CPU 143 | {8E09CB51-03C5-413A-8570-1A121A01B0A4}.Release|Any CPU.ActiveCfg = Release|Any CPU 144 | {8E09CB51-03C5-413A-8570-1A121A01B0A4}.Release|Any CPU.Build.0 = Release|Any CPU 145 | EndGlobalSection 146 | GlobalSection(SolutionProperties) = preSolution 147 | HideSolutionNode = FALSE 148 | EndGlobalSection 149 | GlobalSection(NestedProjects) = preSolution 150 | {5728DE02-C264-4697-B554-DDA5FAD10A47} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 151 | {1BEFE50E-24CA-4CAA-8B2C-7F8CD1771CB5} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 152 | {EC04FD05-F3FE-439A-8791-D85856E3726A} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 153 | {B1453AE4-B343-42F0-B6F6-E9016A62B3C2} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 154 | {6991A5CF-25CB-4336-9F6D-2EB1E01EFB75} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 155 | {718D4969-A6F7-4B0B-8FD2-DE13F23F5860} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 156 | {1709F0A9-F797-44FD-94C0-18A0D75442F8} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 157 | {34C86079-CCAB-4312-98FC-F64F4A97D3A4} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 158 | {501F4158-CF10-4C25-AAA3-7BE827924926} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 159 | {4EC0F237-1CF1-4A0F-B849-E8B75A18CEEB} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 160 | {F302F223-C0CD-46A6-8BB6-01F1213BDD58} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 161 | {63B64572-B72C-4BCE-BB3D-C3F7635A6858} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 162 | {89C81FA0-A06B-4FB9-A3FC-4DAB2A93AA2C} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 163 | {BAD64799-5E68-4EA5-B117-1F0B3582392B} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 164 | {82A07696-48E6-4EA7-9DF6-3422157E861F} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 165 | {C345A6AA-A562-447F-8A2B-63645B4FE56F} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 166 | {ADADC124-AF03-428D-ADD5-9CB2DF88E81C} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 167 | {F36FD7F7-0C41-44E3-89FE-AAF560BA69DC} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 168 | {293F96F9-7E15-4969-B8ED-595D06FD7AA5} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 169 | {8E09CB51-03C5-413A-8570-1A121A01B0A4} = {B34C7F0A-2903-47F2-A087-06D3136C9A1F} 170 | EndGlobalSection 171 | GlobalSection(ExtensibilityGlobals) = postSolution 172 | SolutionGuid = {3F3244FD-DDC3-4CF2-8E66-1B4EDD8109D6} 173 | EndGlobalSection 174 | EndGlobal 175 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 NServiceBus Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Prerequisites 2 | 3 | * [MongoDB](https://www.mongodb.org/downloads) 4 | * SQL Server 5 | * You must create the database before running the apps (see the connection string in the config files) 6 | 7 | ### HashBus.Twitter.Monitor configuration [Optional] 8 | 9 | The `HashBus.Twitter.Monitor.Simulator` allows testing of most HashBus functionality without using the public [Twitter API](https://dev.twitter.com/overview/documentation). In order to run against the public Twitter API the following steps are required: 10 | 11 | * Go to https://apps.twitter.com/ and create a new application; 12 | * On the machine where the `HashBus.Twitter.Monitor` needs to run, create the following environment variables: 13 | * `HASHBUS_TWITTER_CONSUMER_KEY`: Twitter app consumer key 14 | * `HASHBUS_TWITTER_CONSUMER_SECRET`: Twitter app consumer secret 15 | * `HASHBUS_TWITTER_ACCESS_TOKEN`: Twitter app access token 16 | * `HASHBUS_TWITTER_ACCESS_TOKEN_SECRET`: Twitter app token secret 17 | 18 | ### Web API 19 | 20 | The web API is hosted at http://hashbus-demo.cloudapp.net:8080/ 21 | 22 | Available resources are: 23 | 24 | * `http://hashbus-demo.cloudapp.net:8080/top-tweeters-retweeters/{track}` 25 | * `http://hashbus-demo.cloudapp.net:8080/top-tweeters/{track}` 26 | * `http://hashbus-demo.cloudapp.net:8080/top-retweeters/{track}` 27 | * `http://hashbus-demo.cloudapp.net:8080/most-mentioned/{track}` 28 | * `http://hashbus-demo.cloudapp.net:8080/most-retweeted/{track}` 29 | * `http://hashbus-demo.cloudapp.net:8080/most-hashtagged/{track}` 30 | 31 | A 'track' is a Twitter search term. At the time of writing, the HashBus Twitter monitor is running for the [#BuildStuffLT](https://twitter.com/search?q=%23BuildStuffLT) hashtag. 32 | 33 | Here comes the funky thing. There is a [bug in Nancy](https://github.com/NancyFx/Nancy/issues/1154) which prevents a `#` (hash/pound) sign from being used in a URL, even if URL encoded. For this reason we use a special character sequence `해시` to represent `#`. (해시 means "hash" in Korean!) 34 | 35 | Thus, example URL's for #BuildStuffLT are: 36 | 37 | * http://hashbus-demo.cloudapp.net:8080/top-tweeters-retweeters/해시BuildStuffLT 38 | * http://hashbus-demo.cloudapp.net:8080/top-tweeters/해시BuildStuffLT 39 | * http://hashbus-demo.cloudapp.net:8080/top-retweeters/해시BuildStuffLT 40 | * http://hashbus-demo.cloudapp.net:8080/most-mentioned/해시BuildStuffLT 41 | * http://hashbus-demo.cloudapp.net:8080/most-retweeted/해시BuildStuffLT 42 | * http://hashbus-demo.cloudapp.net:8080/most-hashtagged/해시BuildStuffLT 43 | 44 | These URL's will give you a leaderboard object which looks like this: 45 | 46 | ```JSON 47 | { 48 | "entries": [{ 49 | "position": 1, 50 | "id": 1351703234, 51 | "idStr": "1351703234", 52 | "name": "Build Stuff 2015 LT", 53 | "screenName": "BuildStuffLT", 54 | "count": 28 55 | }, 56 | { 57 | "position": 2, 58 | "id": 15528065, 59 | "idStr": "15528065", 60 | "name": "Malk’Zameth", 61 | "screenName": "malk_zameth", 62 | "count": 16 63 | }, 64 | { 65 | "position": 3, 66 | "id": 183551266, 67 | "idStr": "183551266", 68 | "name": "Daniel Lee", 69 | "screenName": "danlimerick", 70 | "count": 15 71 | }, 72 | { 73 | "position": 4, 74 | "id": 235599885, 75 | "idStr": "235599885", 76 | "name": "Peter Even", 77 | "screenName": "petervaneven", 78 | "count": 14 79 | }, 80 | { 81 | "position": 5, 82 | "id": 22696598, 83 | "idStr": "22696598", 84 | "name": "Mauro Servienti", 85 | "screenName": "mauroservienti", 86 | "count": 10 87 | }, 88 | { 89 | "position": 6, 90 | "id": 2511419816, 91 | "idStr": "2511419816", 92 | "name": "Jean-François Saguin", 93 | "screenName": "jfsaguin", 94 | "count": 9 95 | }, 96 | { 97 | "position": 7, 98 | "id": 161837846, 99 | "idStr": "161837846", 100 | "name": "Bouillier Clément", 101 | "screenName": "clem_bouillier", 102 | "count": 7 103 | }, 104 | { 105 | "position": 8, 106 | "id": 14128651, 107 | "idStr": "14128651", 108 | "name": "Grégory Weinbach", 109 | "screenName": "gweinbach", 110 | "count": 7 111 | }, 112 | { 113 | "position": 9, 114 | "id": 8885582, 115 | "idStr": "8885582", 116 | "name": "Rui Carvalho", 117 | "screenName": "rhwy", 118 | "count": 6 119 | }, 120 | { 121 | "position": 10, 122 | "id": 2375271441, 123 | "idStr": "2375271441", 124 | "name": "Ernestas Kardzys", 125 | "screenName": "ErnestasKardzys", 126 | "count": 6 127 | }], 128 | "count": 310, 129 | "since": "2015-11-17T16:17:41.0000000Z" 130 | } 131 | ``` 132 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/HashBus.NServiceBusConfiguration/BusConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.NServiceBusConfiguration 2 | { 3 | using NServiceBus; 4 | 5 | public static class BusConfigurationExtensions 6 | { 7 | public static void ApplyMessageConventions(this EndpointConfiguration configuration) 8 | { 9 | configuration.Conventions() 10 | .DefiningCommandsAs(t => t.Namespace != null && t.Namespace.EndsWith("Commands")) 11 | .DefiningEventsAs(t => t.Namespace != null && t.Namespace.EndsWith("Events")); 12 | } 13 | 14 | public static void ApplyErrorAndAuditQueueSettings(this EndpointConfiguration configuration) 15 | { 16 | configuration.AuditProcessedMessagesTo("audit"); 17 | configuration.SendFailedMessagesTo("error"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/HashBus.NServiceBusConfiguration/HashBus.NServiceBusConfiguration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net461 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostHashtagged/App.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.MostHashtagged 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using HashBus.NServiceBusConfiguration; 7 | using HashBus.ReadModel; 8 | using HashBus.ReadModel.MongoDB; 9 | using MongoDB.Driver; 10 | using NServiceBus; 11 | 12 | class App 13 | { 14 | public static async Task Run(string mongoConnectionString, string mongoDBDatabase, string endpointName, string analyzerAddress) 15 | { 16 | var mongoDatabase = new MongoClient(mongoConnectionString).GetDatabase(mongoDBDatabase); 17 | 18 | var endpointConfiguration = new EndpointConfiguration(endpointName); 19 | endpointConfiguration.UseSerialization(); 20 | endpointConfiguration.EnableInstallers(); 21 | endpointConfiguration.UsePersistence(); 22 | endpointConfiguration.RegisterComponents(c => 23 | c.RegisterSingleton>>( 24 | new MongoDBListRepository(mongoDatabase, "most_hashtagged__hashtags"))); 25 | endpointConfiguration.ApplyMessageConventions(); 26 | endpointConfiguration.ApplyErrorAndAuditQueueSettings(); 27 | endpointConfiguration.LimitMessageProcessingConcurrencyTo(1); 28 | 29 | var transportExtensions = endpointConfiguration.UseTransport(); 30 | 31 | var routing = transportExtensions.Routing(); 32 | routing.RegisterPublisher(typeof(Twitter.Analyzer.Events.TweetAnalyzed), analyzerAddress); 33 | 34 | var endpointInstance = await Endpoint.Start(endpointConfiguration) 35 | .ConfigureAwait(false); 36 | 37 | try 38 | { 39 | await Task.Delay(Timeout.Infinite); 40 | } 41 | finally 42 | { 43 | await endpointInstance.Stop() 44 | .ConfigureAwait(false); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostHashtagged/HashBus.Projector.MostHashtagged.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostHashtagged/MostHashtaggedProjection.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.MostHashtagged 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using ColoredConsole; 8 | using HashBus.ReadModel; 9 | using LiteGuard; 10 | using NServiceBus; 11 | 12 | public class MostHashtaggedProjection : IHandleMessages 13 | { 14 | private readonly IRepository> hashtags; 15 | 16 | public MostHashtaggedProjection(IRepository> hashtags) 17 | { 18 | Guard.AgainstNullArgument(nameof(hashtags), hashtags); 19 | 20 | this.hashtags = hashtags; 21 | } 22 | 23 | public async Task Handle(Twitter.Analyzer.Events.TweetAnalyzed message, IMessageHandlerContext context) 24 | { 25 | if (!message.Tweet.Hashtags.Any()) 26 | { 27 | return; 28 | } 29 | 30 | var trackHashtags = (await this.hashtags.GetAsync(message.Tweet.Track) 31 | .ConfigureAwait(false)).ToList(); 32 | 33 | if (trackHashtags.Any(hashtag => hashtag.TweetId == message.Tweet.Id)) 34 | { 35 | return; 36 | } 37 | 38 | var newHashtags = message.Tweet.Hashtags.Select(hashtag => 39 | new Hashtag 40 | { 41 | HashtaggedAt = message.Tweet.CreatedAt, 42 | TweetId = message.Tweet.Id, 43 | Text = hashtag.Text, 44 | }) 45 | .ToList(); 46 | 47 | trackHashtags.AddRange(newHashtags); 48 | 49 | await this.hashtags.SaveAsync(message.Tweet.Track, trackHashtags) 50 | .ConfigureAwait(false); 51 | 52 | foreach (var hashtag in newHashtags) 53 | { 54 | ColorConsole.WriteLine( 55 | $"{message.Tweet.CreatedAt.ToLocalTime()}".DarkGray(), 56 | " ", 57 | "Added ".Gray(), 58 | $"#{hashtag.Text}".Cyan(), 59 | " usage to ".Gray(), 60 | $" {message.Tweet.Track} ".DarkCyan().On(ConsoleColor.White)); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostHashtagged/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.MostHashtagged 2 | { 3 | using System; 4 | using System.Configuration; 5 | using System.Threading.Tasks; 6 | 7 | class Program 8 | { 9 | static Task Main() 10 | { 11 | var mongoConnectionString = ConfigurationManager.AppSettings["MongoConnectionString"]; 12 | var mongoDBDatabase = ConfigurationManager.AppSettings["MongoDBDatabase"]; 13 | var endpointName = ConfigurationManager.AppSettings["EndpointName"]; 14 | var analyzerAddress = ConfigurationManager.AppSettings["AnalyzerAddress"]; 15 | 16 | Console.Title = typeof(Program).Assembly.GetName().Name; 17 | 18 | return App.Run(mongoConnectionString, mongoDBDatabase, endpointName, analyzerAddress); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostHashtagged/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostMentioned/App.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.MostMentioned 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using HashBus.NServiceBusConfiguration; 7 | using HashBus.ReadModel; 8 | using HashBus.ReadModel.MongoDB; 9 | using MongoDB.Driver; 10 | using NServiceBus; 11 | 12 | class App 13 | { 14 | public static async Task Run(string mongoConnectionString, string mongoDBDatabase, string endpointName, string analyzerAddress) 15 | { 16 | var mongoDatabase = new MongoClient(mongoConnectionString).GetDatabase(mongoDBDatabase); 17 | 18 | var endpointConfiguration = new EndpointConfiguration(endpointName); 19 | endpointConfiguration.UseSerialization(); 20 | endpointConfiguration.EnableInstallers(); 21 | endpointConfiguration.UsePersistence(); 22 | endpointConfiguration.RegisterComponents(c => 23 | c.RegisterSingleton>>( 24 | new MongoDBListRepository(mongoDatabase, "most_mentioned__mentions"))); 25 | endpointConfiguration.ApplyMessageConventions(); 26 | endpointConfiguration.ApplyErrorAndAuditQueueSettings(); 27 | endpointConfiguration.LimitMessageProcessingConcurrencyTo(1); 28 | 29 | var transportExtensions = endpointConfiguration.UseTransport(); 30 | 31 | var routing = transportExtensions.Routing(); 32 | routing.RegisterPublisher(typeof(Twitter.Analyzer.Events.TweetAnalyzed), analyzerAddress); 33 | 34 | var endpointInstance = await Endpoint.Start(endpointConfiguration) 35 | .ConfigureAwait(false); 36 | 37 | try 38 | { 39 | await Task.Delay(Timeout.Infinite); 40 | } 41 | finally 42 | { 43 | await endpointInstance.Stop() 44 | .ConfigureAwait(false); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostMentioned/HashBus.Projector.MostMentioned.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostMentioned/MostMentionedProjection.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.MostMentioned 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using ColoredConsole; 8 | using HashBus.Twitter.Analyzer.Events; 9 | using HashBus.ReadModel; 10 | using LiteGuard; 11 | using NServiceBus; 12 | 13 | public class MostMentionedProjection : IHandleMessages 14 | { 15 | private readonly IRepository> mentions; 16 | 17 | public MostMentionedProjection(IRepository> mentions) 18 | { 19 | Guard.AgainstNullArgument(nameof(mentions), mentions); 20 | 21 | this.mentions = mentions; 22 | } 23 | 24 | public async Task Handle(TweetAnalyzed message, IMessageHandlerContext context) 25 | { 26 | if (!message.Tweet.UserMentions.Any()) 27 | { 28 | return; 29 | } 30 | 31 | var trackMentions = (await this.mentions.GetAsync(message.Tweet.Track) 32 | .ConfigureAwait(false)).ToList(); 33 | 34 | if (trackMentions.Any(mention => mention.TweetId == message.Tweet.Id)) 35 | { 36 | return; 37 | } 38 | 39 | var newMentions = message.Tweet.UserMentions.Select(userMention => 40 | new Mention 41 | { 42 | MentionedAt = message.Tweet.CreatedAt, 43 | TweetId = message.Tweet.Id, 44 | UserMentionId = userMention.Id, 45 | UserMentionName = userMention.Name, 46 | UserMentionScreenName = userMention.ScreenName, 47 | }) 48 | .ToList(); 49 | 50 | trackMentions.AddRange(newMentions); 51 | 52 | await this.mentions.SaveAsync(message.Tweet.Track, trackMentions) 53 | .ConfigureAwait(false); 54 | 55 | foreach (var mention in newMentions) 56 | { 57 | ColorConsole.WriteLine( 58 | $"{message.Tweet.CreatedAt.ToLocalTime()}".DarkGray(), 59 | " ", 60 | "Added ".Gray(), 61 | $"@{mention.UserMentionScreenName}".Cyan(), 62 | " mention to ".Gray(), 63 | $" {message.Tweet.Track} ".DarkCyan().On(ConsoleColor.White)); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostMentioned/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.MostMentioned 2 | { 3 | using System; 4 | using System.Configuration; 5 | using System.Threading.Tasks; 6 | 7 | class Program 8 | { 9 | static Task Main() 10 | { 11 | var mongoConnectionString = ConfigurationManager.AppSettings["MongoConnectionString"]; 12 | var mongoDBDatabase = ConfigurationManager.AppSettings["MongoDBDatabase"]; 13 | var endpointName = ConfigurationManager.AppSettings["EndpointName"]; 14 | var analyzerAddress = ConfigurationManager.AppSettings["AnalyzerAddress"]; 15 | 16 | Console.Title = typeof(Program).Assembly.GetName().Name; 17 | 18 | return App.Run(mongoConnectionString, mongoDBDatabase, endpointName, analyzerAddress); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostMentioned/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostRetweeted/App.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.MostRetweeted 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using HashBus.NServiceBusConfiguration; 7 | using HashBus.ReadModel; 8 | using HashBus.ReadModel.MongoDB; 9 | using MongoDB.Driver; 10 | using NServiceBus; 11 | 12 | class App 13 | { 14 | public static async Task Run(string mongoConnectionString, string mongoDBDatabase, string endpointName, string analyzerAddress) 15 | { 16 | var mongoDatabase = new MongoClient(mongoConnectionString).GetDatabase(mongoDBDatabase); 17 | 18 | var endpointConfiguration = new EndpointConfiguration(endpointName); 19 | endpointConfiguration.UseSerialization(); 20 | endpointConfiguration.EnableInstallers(); 21 | endpointConfiguration.UsePersistence(); 22 | endpointConfiguration.RegisterComponents(c => 23 | c.RegisterSingleton>>( 24 | new MongoDBListRepository(mongoDatabase, "most_retweeted__retweetees"))); 25 | endpointConfiguration.ApplyMessageConventions(); 26 | endpointConfiguration.ApplyErrorAndAuditQueueSettings(); 27 | endpointConfiguration.LimitMessageProcessingConcurrencyTo(1); 28 | 29 | var transportExtensions = endpointConfiguration.UseTransport(); 30 | 31 | var routing = transportExtensions.Routing(); 32 | routing.RegisterPublisher(typeof(Twitter.Analyzer.Events.TweetAnalyzed), analyzerAddress); 33 | 34 | var endpointInstance = await Endpoint.Start(endpointConfiguration) 35 | .ConfigureAwait(false); 36 | 37 | try 38 | { 39 | await Task.Delay(Timeout.Infinite); 40 | } 41 | finally 42 | { 43 | await endpointInstance.Stop() 44 | .ConfigureAwait(false); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostRetweeted/HashBus.Projector.MostRetweeted.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostRetweeted/MostRetweetedProjection.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.MostRetweeted 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using ColoredConsole; 8 | using HashBus.ReadModel; 9 | using LiteGuard; 10 | using NServiceBus; 11 | 12 | public class MostRetweetedProjection : IHandleMessages 13 | { 14 | private readonly IRepository> retweetees; 15 | 16 | public MostRetweetedProjection(IRepository> retweetees) 17 | { 18 | Guard.AgainstNullArgument(nameof(retweetees), retweetees); 19 | 20 | this.retweetees = retweetees; 21 | } 22 | 23 | public async Task Handle(Twitter.Analyzer.Events.TweetAnalyzed message, IMessageHandlerContext context) 24 | { 25 | if (message.Tweet.RetweetedTweet == null) 26 | { 27 | return; 28 | } 29 | 30 | var trackRetweetees = (await this.retweetees.GetAsync(message.Tweet.Track) 31 | .ConfigureAwait(false)).ToList(); 32 | 33 | if (trackRetweetees.Any(tweet => tweet.TweetId == message.Tweet.Id)) 34 | { 35 | return; 36 | } 37 | 38 | var retweetees = new List(); 39 | var retweet = message.Tweet.RetweetedTweet; 40 | while (retweet != null) 41 | { 42 | retweetees.Add(new Retweetee 43 | { 44 | RetweetedAt = message.Tweet.CreatedAt, 45 | TweetId = retweet.Id, 46 | UserId = retweet.CreatedById, 47 | UserName = retweet.CreatedByName, 48 | UserScreenName = retweet.CreatedByScreenName, 49 | }); 50 | 51 | retweet = retweet.RetweetedTweet; 52 | } 53 | 54 | trackRetweetees.AddRange(retweetees); 55 | await this.retweetees.SaveAsync(message.Tweet.Track, trackRetweetees) 56 | .ConfigureAwait(false); 57 | 58 | foreach (var retweetee in retweetees) 59 | { 60 | ColorConsole.WriteLine( 61 | $"{message.Tweet.CreatedAt.ToLocalTime()}".DarkGray(), 62 | " ", 63 | "Added retweet of ".Gray(), 64 | $"@{retweetee.UserScreenName}".Cyan(), 65 | " to ".Gray(), 66 | $" {message.Tweet.Track} ".DarkCyan().On(ConsoleColor.White)); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostRetweeted/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.MostRetweeted 2 | { 3 | using System; 4 | using System.Configuration; 5 | using System.Threading.Tasks; 6 | 7 | class Program 8 | { 9 | static Task Main() 10 | { 11 | var mongoConnectionString = ConfigurationManager.AppSettings["MongoConnectionString"]; 12 | var mongoDBDatabase = ConfigurationManager.AppSettings["MongoDBDatabase"]; 13 | var endpointName = ConfigurationManager.AppSettings["EndpointName"]; 14 | var analyzerAddress = ConfigurationManager.AppSettings["AnalyzerAddress"]; 15 | 16 | Console.Title = typeof(Program).Assembly.GetName().Name; 17 | 18 | return App.Run(mongoConnectionString, mongoDBDatabase, endpointName, analyzerAddress); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/HashBus.Projector.MostRetweeted/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopRetweeters/App.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.TopRetweeters 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using HashBus.NServiceBusConfiguration; 7 | using HashBus.ReadModel; 8 | using HashBus.ReadModel.MongoDB; 9 | using MongoDB.Driver; 10 | using NServiceBus; 11 | 12 | class App 13 | { 14 | public static async Task Run(string mongoConnectionString, string mongoDBDatabase, string endpointName, string analyzerAddress) 15 | { 16 | var mongoDatabase = new MongoClient(mongoConnectionString).GetDatabase(mongoDBDatabase); 17 | 18 | var endpointConfiguration = new EndpointConfiguration(endpointName); 19 | endpointConfiguration.UseSerialization(); 20 | endpointConfiguration.EnableInstallers(); 21 | endpointConfiguration.UsePersistence(); 22 | endpointConfiguration.RegisterComponents(c => 23 | c.RegisterSingleton>>( 24 | new MongoDBListRepository(mongoDatabase, "top_retweeters__retweets"))); 25 | endpointConfiguration.ApplyMessageConventions(); 26 | endpointConfiguration.ApplyErrorAndAuditQueueSettings(); 27 | endpointConfiguration.LimitMessageProcessingConcurrencyTo(1); 28 | 29 | var transportExtensions = endpointConfiguration.UseTransport(); 30 | 31 | var routing = transportExtensions.Routing(); 32 | routing.RegisterPublisher(typeof(Twitter.Analyzer.Events.TweetAnalyzed), analyzerAddress); 33 | 34 | var endpointInstance = await Endpoint.Start(endpointConfiguration) 35 | .ConfigureAwait(false); 36 | 37 | try 38 | { 39 | await Task.Delay(Timeout.Infinite); 40 | } 41 | finally 42 | { 43 | await endpointInstance.Stop() 44 | .ConfigureAwait(false); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopRetweeters/HashBus.Projector.TopRetweeters.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopRetweeters/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.TopRetweeters 2 | { 3 | using System; 4 | using System.Configuration; 5 | using System.Threading.Tasks; 6 | 7 | class Program 8 | { 9 | static Task Main() 10 | { 11 | var mongoConnectionString = ConfigurationManager.AppSettings["MongoConnectionString"]; 12 | var mongoDBDatabase = ConfigurationManager.AppSettings["MongoDBDatabase"]; 13 | var endpointName = ConfigurationManager.AppSettings["EndpointName"]; 14 | var analyzerAddress = ConfigurationManager.AppSettings["AnalyzerAddress"]; 15 | 16 | Console.Title = typeof(Program).Assembly.GetName().Name; 17 | 18 | return App.Run(mongoConnectionString, mongoDBDatabase, endpointName, analyzerAddress); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopRetweeters/TopRetweetersProjection.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.TopRetweeters 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using ColoredConsole; 8 | using HashBus.ReadModel; 9 | using LiteGuard; 10 | using NServiceBus; 11 | 12 | public class TopRetweetersProjection : IHandleMessages 13 | { 14 | private readonly IRepository> tweets; 15 | 16 | public TopRetweetersProjection(IRepository> tweets) 17 | { 18 | Guard.AgainstNullArgument(nameof(tweets), tweets); 19 | 20 | this.tweets = tweets; 21 | } 22 | 23 | public async Task Handle(Twitter.Analyzer.Events.TweetAnalyzed message, IMessageHandlerContext context) 24 | { 25 | if (message.Tweet.RetweetedTweet == null) 26 | { 27 | return; 28 | } 29 | 30 | var trackTweets = (await this.tweets.GetAsync(message.Tweet.Track) 31 | .ConfigureAwait(false)).ToList(); 32 | 33 | if (trackTweets.Any(tweet => tweet.TweetId == message.Tweet.Id)) 34 | { 35 | return; 36 | } 37 | 38 | trackTweets.Add(new Retweet 39 | { 40 | RetweetedAt = message.Tweet.CreatedAt, 41 | TweetId = message.Tweet.Id, 42 | UserId = message.Tweet.CreatedById, 43 | UserName = message.Tweet.CreatedByName, 44 | UserScreenName = message.Tweet.CreatedByScreenName, 45 | }); 46 | 47 | await this.tweets.SaveAsync(message.Tweet.Track, trackTweets) 48 | .ConfigureAwait(false); 49 | 50 | ColorConsole.WriteLine( 51 | $"{message.Tweet.CreatedAt.ToLocalTime()}".DarkGray(), 52 | " ", 53 | "Added ".Gray(), 54 | $"@{message.Tweet.CreatedByScreenName}".Cyan(), 55 | " retweet to ".Gray(), 56 | $" {message.Tweet.Track} ".DarkCyan().On(ConsoleColor.White)); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopRetweeters/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopTweeters/App.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.TopTweeters 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using HashBus.NServiceBusConfiguration; 7 | using HashBus.ReadModel; 8 | using HashBus.ReadModel.MongoDB; 9 | using MongoDB.Driver; 10 | using NServiceBus; 11 | 12 | class App 13 | { 14 | public static async Task Run(string mongoConnectionString, string mongoDBDatabase, string endpointName, string analyzerAddress) 15 | { 16 | var mongoDatabase = new MongoClient(mongoConnectionString).GetDatabase(mongoDBDatabase); 17 | 18 | var endpointConfiguration = new EndpointConfiguration(endpointName); 19 | endpointConfiguration.UseSerialization(); 20 | endpointConfiguration.EnableInstallers(); 21 | endpointConfiguration.UsePersistence(); 22 | endpointConfiguration.RegisterComponents(c => 23 | c.RegisterSingleton>>( 24 | new MongoDBListRepository(mongoDatabase, "top_tweeters__tweets"))); 25 | endpointConfiguration.ApplyMessageConventions(); 26 | endpointConfiguration.ApplyErrorAndAuditQueueSettings(); 27 | endpointConfiguration.LimitMessageProcessingConcurrencyTo(1); 28 | 29 | var transportExtensions = endpointConfiguration.UseTransport(); 30 | 31 | var routing = transportExtensions.Routing(); 32 | routing.RegisterPublisher(typeof(Twitter.Analyzer.Events.TweetAnalyzed), analyzerAddress); 33 | 34 | var endpointInstance = await Endpoint.Start(endpointConfiguration) 35 | .ConfigureAwait(false); 36 | 37 | try 38 | { 39 | await Task.Delay(Timeout.Infinite); 40 | } 41 | finally 42 | { 43 | await endpointInstance.Stop() 44 | .ConfigureAwait(false); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopTweeters/HashBus.Projector.TopTweeters.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopTweeters/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.TopTweeters 2 | { 3 | using System; 4 | using System.Configuration; 5 | using System.Threading.Tasks; 6 | 7 | class Program 8 | { 9 | static Task Main() 10 | { 11 | var mongoConnectionString = ConfigurationManager.AppSettings["MongoConnectionString"]; 12 | var mongoDBDatabase = ConfigurationManager.AppSettings["MongoDBDatabase"]; 13 | var endpointName = ConfigurationManager.AppSettings["EndpointName"]; 14 | var analyzerAddress = ConfigurationManager.AppSettings["AnalyzerAddress"]; 15 | 16 | Console.Title = typeof(Program).Assembly.GetName().Name; 17 | 18 | return App.Run(mongoConnectionString, mongoDBDatabase, endpointName, analyzerAddress); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopTweeters/TopTweetersProjection.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.TopTweeters 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using ColoredConsole; 8 | using HashBus.ReadModel; 9 | using LiteGuard; 10 | using NServiceBus; 11 | 12 | public class TopTweetersProjection : IHandleMessages 13 | { 14 | private readonly IRepository> tweets; 15 | 16 | public TopTweetersProjection(IRepository> tweets) 17 | { 18 | Guard.AgainstNullArgument(nameof(tweets), tweets); 19 | 20 | this.tweets = tweets; 21 | } 22 | 23 | public async Task Handle(Twitter.Analyzer.Events.TweetAnalyzed message, IMessageHandlerContext context) 24 | { 25 | if (message.Tweet.RetweetedTweet != null) 26 | { 27 | return; 28 | } 29 | 30 | var trackTweets = (await this.tweets.GetAsync(message.Tweet.Track) 31 | .ConfigureAwait(false)).ToList(); 32 | 33 | if (trackTweets.Any(tweet => tweet.TweetId == message.Tweet.Id)) 34 | { 35 | return; 36 | } 37 | 38 | trackTweets.Add(new Tweet 39 | { 40 | TweetedAt = message.Tweet.CreatedAt, 41 | TweetId = message.Tweet.Id, 42 | UserId = message.Tweet.CreatedById, 43 | UserName = message.Tweet.CreatedByName, 44 | UserScreenName = message.Tweet.CreatedByScreenName, 45 | }); 46 | 47 | await this.tweets.SaveAsync(message.Tweet.Track, trackTweets) 48 | .ConfigureAwait(false); 49 | 50 | ColorConsole.WriteLine( 51 | $"{message.Tweet.CreatedAt.ToLocalTime()}".DarkGray(), 52 | " ", 53 | "Added ".Gray(), 54 | $"@{message.Tweet.CreatedByScreenName}".Cyan(), 55 | " tweet to ".Gray(), 56 | $" {message.Tweet.Track} ".DarkCyan().On(ConsoleColor.White)); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopTweeters/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopTweetersRetweeters/App.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.TopTweetersRetweeters 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using HashBus.NServiceBusConfiguration; 7 | using HashBus.ReadModel; 8 | using HashBus.ReadModel.MongoDB; 9 | using MongoDB.Driver; 10 | using NServiceBus; 11 | 12 | class App 13 | { 14 | public static async Task Run(string mongoConnectionString, string mongoDBDatabase, string endpointName, string analyzerAddress) 15 | { 16 | var mongoDatabase = new MongoClient(mongoConnectionString).GetDatabase(mongoDBDatabase); 17 | 18 | var endpointConfiguration = new EndpointConfiguration(endpointName); 19 | endpointConfiguration.UseSerialization(); 20 | endpointConfiguration.EnableInstallers(); 21 | endpointConfiguration.UsePersistence(); 22 | endpointConfiguration.RegisterComponents(c => 23 | c.RegisterSingleton>>( 24 | new MongoDBListRepository(mongoDatabase, "top_tweeters_retweeters__tweets_retweets"))); 25 | endpointConfiguration.ApplyMessageConventions(); 26 | endpointConfiguration.ApplyErrorAndAuditQueueSettings(); 27 | endpointConfiguration.LimitMessageProcessingConcurrencyTo(1); 28 | 29 | var transportExtensions = endpointConfiguration.UseTransport(); 30 | 31 | var routing = transportExtensions.Routing(); 32 | routing.RegisterPublisher(typeof(Twitter.Analyzer.Events.TweetAnalyzed), analyzerAddress); 33 | 34 | var endpointInstance = await Endpoint.Start(endpointConfiguration) 35 | .ConfigureAwait(false); 36 | 37 | try 38 | { 39 | await Task.Delay(Timeout.Infinite); 40 | } 41 | finally 42 | { 43 | await endpointInstance.Stop() 44 | .ConfigureAwait(false); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopTweetersRetweeters/HashBus.Projector.TopTweetersRetweeters.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopTweetersRetweeters/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.TopTweetersRetweeters 2 | { 3 | using System; 4 | using System.Configuration; 5 | using System.Threading.Tasks; 6 | 7 | class Program 8 | { 9 | static Task Main() 10 | { 11 | var mongoConnectionString = ConfigurationManager.AppSettings["MongoConnectionString"]; 12 | var mongoDBDatabase = ConfigurationManager.AppSettings["MongoDBDatabase"]; 13 | var endpointName = ConfigurationManager.AppSettings["EndpointName"]; 14 | var analyzerAddress = ConfigurationManager.AppSettings["AnalyzerAddress"]; 15 | 16 | Console.Title = typeof(Program).Assembly.GetName().Name; 17 | 18 | return App.Run(mongoConnectionString, mongoDBDatabase, endpointName, analyzerAddress); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopTweetersRetweeters/TopTweetersRetweetersProjection.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Projector.TopTweetersRetweeters 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using ColoredConsole; 8 | using HashBus.ReadModel; 9 | using LiteGuard; 10 | using NServiceBus; 11 | 12 | public class TopTweetersRetweetersProjection : IHandleMessages 13 | { 14 | private readonly IRepository> tweets; 15 | 16 | public TopTweetersRetweetersProjection(IRepository> tweets) 17 | { 18 | Guard.AgainstNullArgument(nameof(tweets), tweets); 19 | 20 | this.tweets = tweets; 21 | } 22 | 23 | public async Task Handle(Twitter.Analyzer.Events.TweetAnalyzed message, IMessageHandlerContext context) 24 | { 25 | var trackTweets = (await this.tweets.GetAsync(message.Tweet.Track) 26 | .ConfigureAwait(false)).ToList(); 27 | 28 | if (trackTweets.Any(tweet => tweet.TweetId == message.Tweet.Id)) 29 | { 30 | return; 31 | } 32 | 33 | trackTweets.Add(new TweetRetweet 34 | { 35 | TweetedRetweetedAt = message.Tweet.CreatedAt, 36 | TweetId = message.Tweet.Id, 37 | UserId = message.Tweet.CreatedById, 38 | UserName = message.Tweet.CreatedByName, 39 | UserScreenName = message.Tweet.CreatedByScreenName, 40 | }); 41 | 42 | await this.tweets.SaveAsync(message.Tweet.Track, trackTweets) 43 | .ConfigureAwait(false); 44 | 45 | ColorConsole.WriteLine( 46 | $"{message.Tweet.CreatedAt.ToLocalTime()}".DarkGray(), 47 | " ", 48 | "Added ".Gray(), 49 | $"@{message.Tweet.CreatedByScreenName}".Cyan(), 50 | " tweet/retweet to ".Gray(), 51 | $" {message.Tweet.Track} ".DarkCyan().On(ConsoleColor.White)); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/HashBus.Projector.TopTweetersRetweeters/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/HashBus.ReadModel.MongoDB/HashBus.ReadModel.MongoDB.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/HashBus.ReadModel.MongoDB/MongoDBListRepository.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.ReadModel.MongoDB 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using global::MongoDB.Bson.Serialization.Attributes; 7 | using global::MongoDB.Driver; 8 | using LiteGuard; 9 | 10 | public class MongoDBListRepository : IRepository> 11 | { 12 | private readonly IMongoCollection collection; 13 | 14 | public MongoDBListRepository(IMongoDatabase database, string collectionName) 15 | { 16 | Guard.AgainstNullArgument(nameof(database), database); 17 | 18 | this.collection = database.GetCollection(collectionName); 19 | } 20 | 21 | public async Task> GetAsync(string key) 22 | { 23 | key = key?.ToLowerInvariant(); 24 | return (await this.collection.Find(doc => doc.Id == key).ToListAsync()).FirstOrDefault()?.Values 25 | ?? Enumerable.Empty(); 26 | } 27 | 28 | public async Task SaveAsync(string key, IEnumerable value) 29 | { 30 | key = key?.ToLowerInvariant(); 31 | await this.collection.ReplaceOneAsync( 32 | doc => doc.Id == key, new Document { Id = key, Values = value }, new UpdateOptions { IsUpsert = true }); 33 | } 34 | 35 | private class Document 36 | { 37 | [BsonId] 38 | public string Id { get; set; } 39 | 40 | public IEnumerable Values { get; set; } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/HashBus.ReadModel/HashBus.ReadModel.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/HashBus.ReadModel/Hashtag.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.ReadModel 2 | { 3 | using System; 4 | 5 | public class Hashtag 6 | { 7 | public DateTime HashtaggedAt { get; set; } 8 | 9 | public long TweetId { get; set; } 10 | 11 | public string Text { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/HashBus.ReadModel/IRepository.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.ReadModel 2 | { 3 | using System.Threading.Tasks; 4 | 5 | public interface IRepository 6 | { 7 | Task GetAsync(TKey key); 8 | 9 | Task SaveAsync(TKey key, TValue value); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/HashBus.ReadModel/Mention.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.ReadModel 2 | { 3 | using System; 4 | 5 | public class Mention 6 | { 7 | public DateTime MentionedAt { get; set; } 8 | 9 | public long TweetId { get; set; } 10 | 11 | public long? UserMentionId { get; set; } 12 | 13 | public string UserMentionName { get; set; } 14 | 15 | public string UserMentionScreenName { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/HashBus.ReadModel/Retweet.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.ReadModel 2 | { 3 | using System; 4 | 5 | public class Retweet 6 | { 7 | public DateTime RetweetedAt { get; set; } 8 | 9 | public long TweetId { get; set; } 10 | 11 | public long UserId { get; set; } 12 | 13 | public string UserName { get; set; } 14 | 15 | public string UserScreenName { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/HashBus.ReadModel/Retweetee.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.ReadModel 2 | { 3 | using System; 4 | 5 | public class Retweetee 6 | { 7 | public DateTime RetweetedAt { get; set; } 8 | 9 | public long TweetId { get; set; } 10 | 11 | public long UserId { get; set; } 12 | 13 | public string UserName { get; set; } 14 | 15 | public string UserScreenName { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/HashBus.ReadModel/Tweet.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.ReadModel 2 | { 3 | using System; 4 | 5 | public class Tweet 6 | { 7 | public DateTime TweetedAt { get; set; } 8 | 9 | public long TweetId { get; set; } 10 | 11 | public long UserId { get; set; } 12 | 13 | public string UserName { get; set; } 14 | 15 | public string UserScreenName { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/HashBus.ReadModel/TweetRetweet.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.ReadModel 2 | { 3 | using System; 4 | 5 | public class TweetRetweet 6 | { 7 | public DateTime TweetedRetweetedAt { get; set; } 8 | 9 | public long TweetId { get; set; } 10 | 11 | public long UserId { get; set; } 12 | 13 | public string UserName { get; set; } 14 | 15 | public string UserScreenName { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer.Commands/AnalyzeTweet.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Analyzer.Commands 2 | { 3 | public class AnalyzeTweet 4 | { 5 | public Tweet Tweet { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer.Commands/HashBus.Twitter.Analyzer.Commands.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer.Commands/Hashtag.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Analyzer.Commands 2 | { 3 | public class Hashtag 4 | { 5 | public string Text { get; set; } 6 | 7 | public int[] Indices { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer.Commands/Tweet.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Analyzer.Commands 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | public class Tweet 7 | { 8 | public string Track { get; set; } 9 | 10 | public long Id { get; set; } 11 | 12 | public DateTime CreatedAt { get; set; } 13 | 14 | public long CreatedById { get; set; } 15 | 16 | public string CreatedByIdStr { get; set; } 17 | 18 | public string CreatedByName { get; set; } 19 | 20 | public string CreatedByScreenName { get; set; } 21 | 22 | public string Text { get; set; } 23 | 24 | public IList UserMentions { get; set; } 25 | 26 | public IList Hashtags { get; set; } 27 | 28 | public Tweet RetweetedTweet { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer.Commands/UserMention.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Analyzer.Commands 2 | { 3 | using System.Collections.Generic; 4 | 5 | public class UserMention 6 | { 7 | public long Id { get; set; } 8 | 9 | public string IdStr { get; set; } 10 | 11 | public IList Indices { get; set; } 12 | 13 | public string Name { get; set; } 14 | 15 | public string ScreenName { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer.Events/HashBus.Twitter.Analyzer.Events.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer.Events/Hashtag.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Analyzer.Events 2 | { 3 | public class Hashtag 4 | { 5 | public string Text { get; set; } 6 | 7 | public int[] Indices { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer.Events/Tweet.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Analyzer.Events 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | public class Tweet 7 | { 8 | public string Track { get; set; } 9 | 10 | public long Id { get; set; } 11 | 12 | public DateTime CreatedAt { get; set; } 13 | 14 | public long CreatedById { get; set; } 15 | 16 | public string CreatedByName { get; set; } 17 | 18 | public string CreatedByScreenName { get; set; } 19 | 20 | public string Text { get; set; } 21 | 22 | public IList UserMentions { get; set; } 23 | 24 | public IList Hashtags { get; set; } 25 | 26 | public Tweet RetweetedTweet { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer.Events/TweetAnalyzed.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Analyzer.Events 2 | { 3 | public class TweetAnalyzed 4 | { 5 | public Tweet Tweet { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer.Events/UserMention.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Analyzer.Events 2 | { 3 | using System.Collections.Generic; 4 | 5 | public class UserMention 6 | { 7 | public long Id { get; set; } 8 | 9 | public IList Indices { get; set; } 10 | 11 | public string Name { get; set; } 12 | 13 | public string ScreenName { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer/AnalyzeTweetHandler.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Analyzer 2 | { 3 | using System; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using HashBus.Twitter.Analyzer.Commands; 7 | using HashBus.Twitter.Analyzer.Events; 8 | using NServiceBus; 9 | 10 | public class AnalyzeTweetHandler : IHandleMessages 11 | { 12 | public Task Handle(AnalyzeTweet message, IMessageHandlerContext context) 13 | { 14 | message.Tweet.UserMentions = message.Tweet.UserMentions 15 | .Where(userMention => 16 | message.Tweet.CreatedById != userMention.Id && 17 | message.Tweet.CreatedByIdStr != userMention.IdStr && 18 | message.Tweet.CreatedByScreenName != userMention.ScreenName && 19 | (message.Tweet.RetweetedTweet == null || 20 | (message.Tweet.RetweetedTweet.CreatedById != userMention.Id && 21 | message.Tweet.RetweetedTweet.CreatedByIdStr != userMention.IdStr && 22 | message.Tweet.RetweetedTweet.CreatedByScreenName != userMention.ScreenName)) && 23 | message.Tweet.Text.Substring(0, userMention.Indices[0]).Trim().ToUpperInvariant() != "RT") 24 | .GroupBy(userMention => userMention.Id) 25 | .Select(group => group.First()) 26 | .ToList(); 27 | 28 | message.Tweet.Hashtags = message.Tweet.Hashtags 29 | .Where(hashtag => !string.Equals($"#{hashtag.Text}", message.Tweet.Track, StringComparison.OrdinalIgnoreCase)) 30 | .GroupBy(hashtag => hashtag.Text.ToUpperInvariant()) 31 | .Select(group => group.First()) 32 | .ToList(); 33 | 34 | Writer.Write(message.Tweet); 35 | 36 | return context.Publish(new TweetAnalyzed { Tweet = TweetMapper.Map(message.Tweet) }); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer/App.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Analyzer 2 | { 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using HashBus.NServiceBusConfiguration; 6 | using NServiceBus; 7 | using NServiceBus.Persistence; 8 | 9 | class App 10 | { 11 | public static async Task Run(string nserviceBusConnectionString, string endpointName) 12 | { 13 | var endpointConfiguration = new EndpointConfiguration(endpointName); 14 | endpointConfiguration.UseSerialization(); 15 | endpointConfiguration.EnableInstallers(); 16 | endpointConfiguration.UsePersistence().ConnectionString(nserviceBusConnectionString); 17 | endpointConfiguration.ApplyMessageConventions(); 18 | endpointConfiguration.ApplyErrorAndAuditQueueSettings(); 19 | endpointConfiguration.LimitMessageProcessingConcurrencyTo(1); 20 | 21 | var endpointInstance = await Endpoint.Start(endpointConfiguration) 22 | .ConfigureAwait(false); 23 | 24 | try 25 | { 26 | await Task.Delay(Timeout.Infinite); 27 | } 28 | finally 29 | { 30 | await endpointInstance.Stop() 31 | .ConfigureAwait(false); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer/HashBus.Twitter.Analyzer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Analyzer 2 | { 3 | using System; 4 | using System.Configuration; 5 | using System.Threading.Tasks; 6 | 7 | class Program 8 | { 9 | static Task Main() 10 | { 11 | var nserviceBusConnectionString = ConfigurationManager.AppSettings["NServiceBusConnectionString"]; 12 | var endpointName = ConfigurationManager.AppSettings["EndpointName"]; 13 | 14 | Console.Title = typeof(Program).Assembly.GetName().Name; 15 | 16 | return App.Run(nserviceBusConnectionString, endpointName); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer/TweetMapper.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Analyzer 2 | { 3 | using System.Linq; 4 | using HashBus.Twitter.Analyzer.Events; 5 | 6 | static class TweetMapper 7 | { 8 | public static Tweet Map(Commands.Tweet tweet) 9 | { 10 | return tweet == null 11 | ? null 12 | : new Tweet 13 | { 14 | CreatedAt = tweet.CreatedAt, 15 | CreatedById = tweet.CreatedById, 16 | CreatedByName = tweet.CreatedByName, 17 | CreatedByScreenName = tweet.CreatedByScreenName, 18 | Hashtags = tweet.Hashtags 19 | .Select(hashTag => 20 | new Hashtag 21 | { 22 | Text = hashTag.Text, 23 | Indices = hashTag.Indices, 24 | }) 25 | .ToList(), 26 | Id = tweet.Id, 27 | RetweetedTweet = Map(tweet.RetweetedTweet), 28 | Text = tweet.Text, 29 | Track = tweet.Track, 30 | UserMentions = tweet.UserMentions 31 | .Select(userMention => 32 | new UserMention 33 | { 34 | Id = userMention.Id, 35 | Indices = userMention.Indices, 36 | Name = userMention.Name, 37 | ScreenName = userMention.ScreenName, 38 | }) 39 | .ToList(), 40 | }; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Analyzer/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.BackFill/App.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.BackFill 2 | { 3 | using System.Threading.Tasks; 4 | using HashBus.NServiceBusConfiguration; 5 | using HashBus.Twitter.CatchUp.Commands; 6 | using NServiceBus; 7 | using NServiceBus.Persistence; 8 | 9 | class App 10 | { 11 | public static async Task Run( 12 | string nserviceBusConnectionString, 13 | string endpointName, 14 | string track, 15 | long tweetId, 16 | string catchUpAddress) 17 | { 18 | var endpointConfiguration = new EndpointConfiguration(endpointName); 19 | endpointConfiguration.UseSerialization(); 20 | endpointConfiguration.EnableInstallers(); 21 | endpointConfiguration.UsePersistence().ConnectionString(nserviceBusConnectionString); 22 | endpointConfiguration.ApplyMessageConventions(); 23 | endpointConfiguration.ApplyErrorAndAuditQueueSettings(); 24 | 25 | var transportExtensions = endpointConfiguration.UseTransport(); 26 | 27 | var routing = transportExtensions.Routing(); 28 | routing.RouteToEndpoint(typeof(StartCatchUp), catchUpAddress); 29 | 30 | var command = new StartCatchUp 31 | { 32 | EndpointName = endpointName, 33 | Track = track, 34 | TweetId = tweetId, 35 | }; 36 | 37 | var endpointInstance = await Endpoint.Start(endpointConfiguration) 38 | .ConfigureAwait(false); 39 | 40 | try 41 | { 42 | 43 | await endpointInstance.Send(command) 44 | .ConfigureAwait(false); 45 | } 46 | finally 47 | { 48 | await endpointInstance.Stop() 49 | .ConfigureAwait(false); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.BackFill/HashBus.Twitter.BackFill.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.BackFill/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.BackFill 2 | { 3 | using System; 4 | using System.Configuration; 5 | using System.Threading.Tasks; 6 | 7 | class Program 8 | { 9 | static Task Main() 10 | { 11 | var nserviceBusConnectionString = ConfigurationManager.AppSettings["NServiceBusConnectionString"]; 12 | var endpointName = ConfigurationManager.AppSettings["EndpointName"]; 13 | var track = ConfigurationManager.AppSettings["Track"]; 14 | var tweetId = long.Parse(ConfigurationManager.AppSettings["TweetId"]); 15 | var catchUpAddress = ConfigurationManager.AppSettings["CatchUpAddress"]; 16 | 17 | Console.Title = typeof(Program).Assembly.GetName().Name; 18 | 19 | return App.Run(nserviceBusConnectionString, endpointName, track, tweetId, catchUpAddress); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.BackFill/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.CatchUp.Commands/HashBus.Twitter.CatchUp.Commands.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.CatchUp.Commands/StartCatchUp.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.CatchUp.Commands 2 | { 3 | public class StartCatchUp 4 | { 5 | public long TweetId { get; set; } 6 | 7 | public string Track { get; set; } 8 | 9 | public string EndpointName { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.CatchUp/App.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.CatchUp 2 | { 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using HashBus.NServiceBusConfiguration; 7 | using HashBus.Twitter.Analyzer.Commands; 8 | using HashBus.Twitter.CatchUp.Commands; 9 | using HashBus.Twitter.Monitor.Events; 10 | using NServiceBus; 11 | using NServiceBus.Persistence; 12 | 13 | class App 14 | { 15 | public static async Task Run( 16 | int maximumNumberOfTweetsPerCatchUp, 17 | TimeSpan defaultTransactionTimeout, 18 | string nserviceBusConnectionString, 19 | string endpointName, 20 | string consumerKey, 21 | string consumerSecret, 22 | string accessToken, 23 | string accessTokenSecret, 24 | string catchUpAddress, 25 | string analyzerAddress, 26 | string monitorAddress) 27 | { 28 | var endpointConfiguration = new EndpointConfiguration(endpointName); 29 | 30 | var transportExtensions = endpointConfiguration.UseTransport() 31 | .Transactions(TransportTransactionMode.ReceiveOnly); 32 | 33 | endpointConfiguration.UnitOfWork().WrapHandlersInATransactionScope(defaultTransactionTimeout); 34 | endpointConfiguration.UseSerialization(); 35 | endpointConfiguration.EnableInstallers(); 36 | endpointConfiguration.UsePersistence().ConnectionString(nserviceBusConnectionString); 37 | endpointConfiguration.ApplyMessageConventions(); 38 | endpointConfiguration.ApplyErrorAndAuditQueueSettings(); 39 | endpointConfiguration.RegisterComponents(c=>c.RegisterSingleton( 40 | new TweetService(maximumNumberOfTweetsPerCatchUp, consumerKey, consumerSecret, accessToken, accessTokenSecret))); 41 | endpointConfiguration.LimitMessageProcessingConcurrencyTo(1); 42 | 43 | var routing = transportExtensions.Routing(); 44 | routing.RouteToEndpoint(typeof(StartCatchUp), catchUpAddress); 45 | routing.RouteToEndpoint(typeof(AnalyzeTweet), analyzerAddress); 46 | routing.RegisterPublisher(typeof(TweetReceived), monitorAddress); 47 | 48 | var endpointInstance = await Endpoint.Start(endpointConfiguration) 49 | .ConfigureAwait(false); 50 | 51 | try 52 | { 53 | await Task.Delay(Timeout.Infinite); 54 | } 55 | finally 56 | { 57 | await endpointInstance.Stop() 58 | .ConfigureAwait(false); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.CatchUp/HashBus.Twitter.CatchUp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.CatchUp/ITweetService.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.CatchUp 2 | { 3 | using System.Collections.Generic; 4 | using Tweetinvi.Core.Interfaces; 5 | 6 | public interface ITweetService 7 | { 8 | IEnumerable Get(string track, long sinceTweetId); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.CatchUp/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.CatchUp 2 | { 3 | using System; 4 | using System.Configuration; 5 | using System.Threading.Tasks; 6 | 7 | class Program 8 | { 9 | static Task Main() 10 | { 11 | var maximumNumberOfTweetsPerCatchUp = int.Parse(ConfigurationManager.AppSettings["MaximumNumberOfTweetsPerCatchUp"]); 12 | var defaultTransactionTimeout = TimeSpan.Parse(ConfigurationManager.AppSettings["defaultTransactionTimeout"]); 13 | var nserviceBusConnectionString = ConfigurationManager.AppSettings["NServiceBusConnectionString"]; 14 | var endpointName = ConfigurationManager.AppSettings["EndpointName"]; 15 | var catchUpAddress = ConfigurationManager.AppSettings["CatchUpAddress"]; 16 | var analyzerAddress = ConfigurationManager.AppSettings["AnalyzerAddress"]; 17 | var monitorAddress = ConfigurationManager.AppSettings["MonitorAddress"]; 18 | 19 | var consumerKeyName = "HASHBUS_TWITTER_CONSUMER_KEY"; 20 | var consumerSecretKeyName = "HASHBUS_TWITTER_CONSUMER_SECRET"; 21 | var accessTokenSecretKeyName = "HASHBUS_TWITTER_ACCESS_TOKEN_SECRET"; 22 | var accessTokenKeyName = "HASHBUS_TWITTER_ACCESS_TOKEN"; 23 | 24 | var consumerKey = Environment.GetEnvironmentVariable(consumerKeyName); 25 | if (consumerKey == null) 26 | { 27 | throw new Exception($"{consumerKeyName} enviroment variable is not set."); 28 | } 29 | 30 | var consumerSecret = Environment.GetEnvironmentVariable(consumerSecretKeyName); 31 | if (consumerSecret == null) 32 | { 33 | throw new Exception($"{consumerSecretKeyName} enviroment variable is not set."); 34 | } 35 | 36 | var accessToken = Environment.GetEnvironmentVariable(accessTokenKeyName); 37 | if (accessToken == null) 38 | { 39 | throw new Exception($"{accessTokenKeyName} enviroment variable is not set."); 40 | } 41 | 42 | var accessTokenSecret = Environment.GetEnvironmentVariable(accessTokenSecretKeyName); 43 | if (accessTokenSecret == null) 44 | { 45 | throw new Exception($"{accessTokenSecretKeyName} enviroment variable is not set."); 46 | } 47 | 48 | Console.Title = typeof(Program).Assembly.GetName().Name; 49 | 50 | return App.Run( 51 | maximumNumberOfTweetsPerCatchUp, 52 | defaultTransactionTimeout, 53 | nserviceBusConnectionString, 54 | endpointName, 55 | consumerKey, 56 | consumerSecret, 57 | accessToken, 58 | accessTokenSecret, 59 | catchUpAddress, 60 | analyzerAddress, 61 | monitorAddress); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.CatchUp/StartCatchUpHandler.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.CatchUp 2 | { 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using ColoredConsole; 6 | using HashBus.Twitter.CatchUp.Commands; 7 | using NServiceBus; 8 | 9 | public class StartCatchUpHandler : IHandleMessages 10 | { 11 | private readonly ITweetService tweetService; 12 | 13 | public StartCatchUpHandler(ITweetService tweetService) 14 | { 15 | this.tweetService = tweetService; 16 | } 17 | 18 | public async Task Handle(StartCatchUp message, IMessageHandlerContext context) 19 | { 20 | ColorConsole.WriteLine( 21 | "Catching up on".Gray(), 22 | " ", 23 | $" {message.Track} ".DarkCyan().OnWhite(), 24 | " ", 25 | "tweets since tweet".Gray(), 26 | " ", 27 | $"{message.TweetId}".White()); 28 | 29 | var count = 0; 30 | foreach(var analyzeTweet in 31 | this.tweetService.Get(message.Track, message.TweetId).Select(tweet => TweetMapper.Map(tweet, message.Track))) 32 | { 33 | ++count; 34 | Writer.Write(analyzeTweet.Tweet); 35 | await context.Send(analyzeTweet) 36 | .ConfigureAwait(false); 37 | } 38 | 39 | ColorConsole.WriteLine( 40 | "Found".Gray(), 41 | " ", 42 | $"{count:N0}".White(), 43 | " ", 44 | $" {message.Track} ".DarkCyan().OnWhite(), 45 | " ", 46 | $"tweet{(count == 1 ? "" : "s")} since tweet".Gray(), 47 | " ", 48 | $"{message.TweetId}".White()); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.CatchUp/TweetReceivedSaga.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.CatchUp 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | using ColoredConsole; 6 | using HashBus.Twitter.CatchUp.Commands; 7 | using HashBus.Twitter.Monitor.Events; 8 | using NServiceBus; 9 | 10 | public class TweetReceivedSaga : Saga, 11 | IAmStartedByMessages 12 | { 13 | public async Task Handle(TweetReceived message, IMessageHandlerContext context) 14 | { 15 | if (Data.PreviousSessionId != Guid.Empty && Data.PreviousSessionId != message.SessionId) 16 | { 17 | ColorConsole.WriteLine( 18 | $" {message.Track} ".DarkCyan().OnWhite(), 19 | " ", 20 | "needs catch up from tweet".Gray(), 21 | " ", 22 | $"{Data.PreviousTweetId}".White()); 23 | 24 | await context.Send(new StartCatchUp 25 | { 26 | TweetId = Data.PreviousTweetId, 27 | Track = message.Track, 28 | }) 29 | .ConfigureAwait(false); 30 | } 31 | 32 | Data.PreviousSessionId = message.SessionId; 33 | Data.Hashtag = message.Track; 34 | Data.PreviousTweetId = message.TweetId; 35 | } 36 | 37 | /// 38 | /// Track and EndpointNmae are a composite key, so this is handled in . 39 | /// > 40 | protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) 41 | { 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.CatchUp/TweetReceivedSagaData.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.CatchUp 2 | { 3 | using System; 4 | using NServiceBus; 5 | 6 | public class TweetReceivedSagaData : ContainSagaData 7 | { 8 | public virtual string Hashtag { get; set; } 9 | 10 | public virtual Guid PreviousSessionId { get; set; } 11 | 12 | public virtual long PreviousTweetId { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.CatchUp/TweetReceivedSagaFinder.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.CatchUp 2 | { 3 | using System.Threading.Tasks; 4 | using HashBus.Twitter.Monitor.Events; 5 | using NServiceBus; 6 | using NServiceBus.Extensibility; 7 | using NServiceBus.Persistence; 8 | using NServiceBus.Sagas; 9 | 10 | class TweetReceivedSagaFinder : IFindSagas.Using 11 | { 12 | public Task FindBy(TweetReceived message, SynchronizedStorageSession storageSession, ReadOnlyContextBag context) 13 | { 14 | var tweetReceivedSagaData = storageSession.Session().QueryOver() 15 | .Where(d => d.Hashtag == message.Track) 16 | .SingleOrDefault(); 17 | 18 | return Task.FromResult(tweetReceivedSagaData); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.CatchUp/TweetService.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.CatchUp 2 | { 3 | using System.Collections.Generic; 4 | using Tweetinvi; 5 | using Tweetinvi.Core.Credentials; 6 | using Tweetinvi.Core.Interfaces; 7 | using Tweetinvi.Core.Parameters; 8 | 9 | class TweetService : ITweetService 10 | { 11 | private readonly int maximumNumberOfTweets; 12 | 13 | public TweetService(int maximumNumberOfTweets, string consumerKey, string consumerSecret, string accessToken, string accessTokenSecret) 14 | { 15 | this.maximumNumberOfTweets = maximumNumberOfTweets; 16 | Auth.SetCredentials(new TwitterCredentials(consumerKey, consumerSecret, accessToken, accessTokenSecret)); 17 | } 18 | 19 | public IEnumerable Get(string track, long sinceTweetId) 20 | { 21 | return Search 22 | .SearchTweets(new TweetSearchParameters(track) { SinceId = sinceTweetId, MaximumNumberOfResults = this.maximumNumberOfTweets }); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.CatchUp/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor.Events/HashBus.Twitter.Monitor.Events.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor.Events/TweetReceived.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Monitor.Events 2 | { 3 | using System; 4 | 5 | public class TweetReceived 6 | { 7 | public string Track { get; set; } 8 | 9 | public long TweetId { get; set; } 10 | 11 | public Guid SessionId { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor.Simulator/App.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Monitor.Simulator 2 | { 3 | using System.Threading.Tasks; 4 | using HashBus.NServiceBusConfiguration; 5 | using HashBus.Twitter.Analyzer.Commands; 6 | using NServiceBus; 7 | using NServiceBus.Persistence; 8 | 9 | class App 10 | { 11 | public static async Task Run(string nserviceBusConnectionString, string hashtag, string endpointName, string analyzerAddress) 12 | { 13 | var endpointConfiguration = new EndpointConfiguration(endpointName); 14 | endpointConfiguration.UseSerialization(); 15 | endpointConfiguration.EnableInstallers(); 16 | endpointConfiguration.UsePersistence().ConnectionString(nserviceBusConnectionString); 17 | endpointConfiguration.ApplyMessageConventions(); 18 | endpointConfiguration.ApplyErrorAndAuditQueueSettings(); 19 | 20 | var transportExtensions = endpointConfiguration.UseTransport(); 21 | 22 | var routing = transportExtensions.Routing(); 23 | routing.RouteToEndpoint(typeof(AnalyzeTweet), analyzerAddress); 24 | 25 | var endpointInstance = await Endpoint.Start(endpointConfiguration) 26 | .ConfigureAwait(false); 27 | 28 | try 29 | { 30 | await Simulation.Start(endpointInstance, hashtag) 31 | .ConfigureAwait(false); 32 | } 33 | finally 34 | { 35 | await endpointInstance.Stop() 36 | .ConfigureAwait(false); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor.Simulator/HashBus.Twitter.Monitor.Simulator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor.Simulator/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Monitor.Simulator 2 | { 3 | using System; 4 | using System.Configuration; 5 | using System.Threading.Tasks; 6 | 7 | class Program 8 | { 9 | static Task Main() 10 | { 11 | var hashtag = ConfigurationManager.AppSettings["Hashtag"]; 12 | var nserviceBusConnectionString = ConfigurationManager.AppSettings["NServiceBusConnectionString"]; 13 | var endpointName = ConfigurationManager.AppSettings["EndpointName"]; 14 | var analyzerAddress = ConfigurationManager.AppSettings["AnalyzerAddress"]; 15 | 16 | Console.Title = typeof(Program).Assembly.GetName().Name; 17 | 18 | return App.Run(nserviceBusConnectionString, hashtag, endpointName, analyzerAddress); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor.Simulator/Simulation.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Monitor.Simulator 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading; 6 | using NServiceBus; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using HashBus.Twitter.Analyzer.Commands; 10 | 11 | class Simulation 12 | { 13 | public static async Task Start(IEndpointInstance endpointInstance, string hashtag) 14 | { 15 | var userNames = new[] 16 | { 17 | "Erik", 18 | "ama", 19 | "Karekin", 20 | "cosima", 21 | "Kamyrn", 22 | "ajay", 23 | "Timaios", 24 | "mila", 25 | "Odilia", 26 | "randi", 27 | "Kennard", 28 | "ilike", 29 | "Rab", 30 | "yolonda", 31 | "Ikaia", 32 | }; 33 | 34 | var secondaryHashtags = new[] 35 | { 36 | "csharp", 37 | "fsharp", 38 | "nservicebus", 39 | "azure", 40 | "dotnet", 41 | "oss", 42 | "javascript", 43 | "microservices", 44 | "serverless", 45 | "ddd", 46 | "aspnetcore", 47 | }; 48 | 49 | var random = new Random(); 50 | 51 | while (true) 52 | { 53 | Thread.Sleep((int)Math.Pow(random.Next(6), 5)); 54 | var now = DateTime.UtcNow; 55 | var track = $"#{hashtag}"; 56 | var hashtagText = track; 57 | var secondaryHashtag = secondaryHashtags[random.Next(secondaryHashtags.Length)]; 58 | 59 | if (now.Millisecond % 3 == 0) 60 | { 61 | hashtag = hashtag.ToLowerInvariant(); 62 | hashtagText = hashtagText.ToLowerInvariant(); 63 | secondaryHashtag = secondaryHashtag.ToLowerInvariant(); 64 | } 65 | 66 | if (now.Millisecond % 7 == 0) 67 | { 68 | hashtag = hashtag.ToUpperInvariant(); 69 | hashtagText = hashtagText.ToUpperInvariant(); 70 | secondaryHashtag = secondaryHashtag.ToUpperInvariant(); 71 | } 72 | 73 | var userId = random.Next(userNames.Length); 74 | var userMentionId = random.Next(userNames.Length); 75 | var userMentionIndex = random.Next(31) + 1; 76 | var retweetedUserId = random.Next(userNames.Length); 77 | var text = string.Join( 78 | string.Empty, 79 | Enumerable.Range(0, userMentionIndex - 1).Select(i => char.ConvertFromUtf32(random.Next(65, 128)))) + 80 | $" {userNames[userMentionId]} " + 81 | string.Join( 82 | string.Empty, 83 | Enumerable.Range(0, random.Next(32)).Select(i => char.ConvertFromUtf32(random.Next(65, 128)))) + 84 | $" {hashtagText}" + 85 | $" #{secondaryHashtag}"; 86 | 87 | var message = new AnalyzeTweet 88 | { 89 | Tweet = new Tweet 90 | { 91 | Track = track, 92 | Id = now.Ticks, 93 | CreatedAt = now, 94 | CreatedById = userId, 95 | CreatedByIdStr = $"{userId}", 96 | CreatedByName = userNames[userId], 97 | CreatedByScreenName = userNames[userId], 98 | Text = text, 99 | UserMentions = new List 100 | { 101 | new UserMention 102 | { 103 | Id=userMentionId, 104 | IdStr= $"{userMentionId}", 105 | Indices = new List { userMentionIndex, userMentionIndex + userNames[userMentionId].Length, }, 106 | Name = userNames[userMentionId], 107 | ScreenName = userNames[userMentionId], 108 | }, 109 | }, 110 | Hashtags = new List 111 | { 112 | new Hashtag 113 | { 114 | Text = hashtag, 115 | Indices = new[] { text.Length - $"{hashtagText} #{secondaryHashtag}".Length, text.Length - $" #{secondaryHashtag}".Length, }, 116 | }, 117 | new Hashtag 118 | { 119 | Text = secondaryHashtag, 120 | Indices = new[] { text.Length - $"#{secondaryHashtag}".Length, text.Length, }, 121 | }, 122 | }, 123 | RetweetedTweet = now.Millisecond % 3 == 0 124 | ? new Tweet 125 | { 126 | Track = track, 127 | Id = now.AddDays(-1000).Ticks, 128 | CreatedAt = now.AddDays(-1000), 129 | CreatedById = retweetedUserId, 130 | CreatedByIdStr = $"{retweetedUserId}", 131 | CreatedByName = userNames[retweetedUserId], 132 | CreatedByScreenName = userNames[retweetedUserId], 133 | Text = text, 134 | UserMentions = new List(), 135 | Hashtags = new List(), 136 | RetweetedTweet = null, 137 | } 138 | : null, 139 | } 140 | }; 141 | 142 | Writer.Write(message.Tweet); 143 | await endpointInstance.Send(message) 144 | .ConfigureAwait(false); 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor.Simulator/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor/App.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Monitor 2 | { 3 | using System.Threading.Tasks; 4 | using HashBus.NServiceBusConfiguration; 5 | using HashBus.Twitter.Analyzer.Commands; 6 | using NServiceBus; 7 | using NServiceBus.Persistence; 8 | 9 | class App 10 | { 11 | public static async Task Run( 12 | string nserviceBusConnectionString, 13 | string track, 14 | string consumerKey, 15 | string consumerSecret, 16 | string accessToken, 17 | string accessTokenSecret, 18 | string endpointName, 19 | string analyzerAddress) 20 | { 21 | var endpointConfiguration = new EndpointConfiguration(endpointName); 22 | endpointConfiguration.UseSerialization(); 23 | endpointConfiguration.EnableInstallers(); 24 | endpointConfiguration.UsePersistence().ConnectionString(nserviceBusConnectionString); 25 | endpointConfiguration.ApplyMessageConventions(); 26 | endpointConfiguration.ApplyErrorAndAuditQueueSettings(); 27 | 28 | var transportExtensions = endpointConfiguration.UseTransport(); 29 | 30 | var routing = transportExtensions.Routing(); 31 | routing.RouteToEndpoint(typeof(AnalyzeTweet), analyzerAddress); 32 | 33 | var endpointInstance = await Endpoint.Start(endpointConfiguration) 34 | .ConfigureAwait(false); 35 | 36 | try 37 | { 38 | await Monitoring.StartAsync( 39 | endpointInstance, 40 | track, 41 | consumerKey, 42 | consumerSecret, 43 | accessToken, 44 | accessTokenSecret) 45 | .ConfigureAwait(false); 46 | } 47 | finally 48 | { 49 | await endpointInstance.Stop() 50 | .ConfigureAwait(false); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor/HashBus.Twitter.Monitor.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor/Monitoring.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Monitor 2 | { 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using ColoredConsole; 7 | using HashBus.Twitter.Monitor.Events; 8 | using NServiceBus; 9 | using Tweetinvi; 10 | using Tweetinvi.Core.Credentials; 11 | 12 | class Monitoring 13 | { 14 | public static async Task StartAsync( 15 | IEndpointInstance endpointInstance, 16 | string track, 17 | string consumerKey, 18 | string consumerSecret, 19 | string accessToken, 20 | string accessTokenSecret) 21 | { 22 | var credentials = new TwitterCredentials(consumerKey, consumerSecret, accessToken, accessTokenSecret); 23 | while (true) 24 | { 25 | try 26 | { 27 | var stream = Stream.CreateFilteredStream(credentials); 28 | stream.AddTrack(track); 29 | 30 | var sessionId = Guid.NewGuid(); 31 | stream.StreamStarted += (sender, args) => 32 | { 33 | sessionId = Guid.NewGuid(); 34 | ColorConsole.WriteLine( 35 | $"{DateTime.UtcNow.ToLocalTime()}".DarkGray(), 36 | " ", 37 | $" {track} ".DarkCyan().OnWhite(), 38 | " ", 39 | "stream started with session ID".Gray(), 40 | " ", 41 | $"{sessionId}".White()); 42 | }; 43 | 44 | stream.StreamStopped += (sender, args) => ColorConsole.WriteLine( 45 | $"{DateTime.UtcNow.ToLocalTime()} ".DarkGray(), 46 | $" {track} ".DarkCyan().OnWhite(), 47 | " stream stopped.".Red(), 48 | args.Exception == null ? string.Empty : $" {args.Exception.Message}".DarkRed()); 49 | 50 | stream.MatchingTweetReceived += async (sender, e) => 51 | { 52 | var analyzeTweet = TweetMapper.Map(e.Tweet, track); 53 | Writer.Write(analyzeTweet.Tweet); 54 | await endpointInstance.Send(analyzeTweet) 55 | .ConfigureAwait(false); 56 | 57 | var tweetReceived = new TweetReceived() 58 | { 59 | SessionId = sessionId, 60 | Track = track, 61 | TweetId = e.Tweet.Id 62 | }; 63 | await endpointInstance.Publish(tweetReceived) 64 | .ConfigureAwait(false); 65 | }; 66 | 67 | await stream.StartStreamMatchingAnyConditionAsync(); 68 | } 69 | catch (Exception ex) 70 | { 71 | ColorConsole.WriteLine($"{DateTime.UtcNow.ToLocalTime()} ".DarkGray(), "Error listening to Twitter stream.".Red(), $" {ex.Message}".DarkRed()); 72 | Thread.Sleep(1000); 73 | } 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter.Monitor 2 | { 3 | using System; 4 | using System.Configuration; 5 | using System.Threading.Tasks; 6 | 7 | class Program 8 | { 9 | static Task Main() 10 | { 11 | var track = ConfigurationManager.AppSettings["Track"]; 12 | var nserviceBusConnectionString = ConfigurationManager.AppSettings["NServiceBusConnectionString"]; 13 | var endpointName = ConfigurationManager.AppSettings["EndpointName"]; 14 | var analyzerAddress = ConfigurationManager.AppSettings["AnalyzerAddress"]; 15 | 16 | var consumerKeyName = "HASHBUS_TWITTER_CONSUMER_KEY"; 17 | var consumerSecretKeyName = "HASHBUS_TWITTER_CONSUMER_SECRET"; 18 | var accessTokenSecretKeyName = "HASHBUS_TWITTER_ACCESS_TOKEN_SECRET"; 19 | var accessTokenKeyName = "HASHBUS_TWITTER_ACCESS_TOKEN"; 20 | 21 | var consumerKey = Environment.GetEnvironmentVariable(consumerKeyName); 22 | if (consumerKey == null) 23 | { 24 | throw new Exception($"{consumerKeyName} enviroment variable is not set."); 25 | } 26 | 27 | var consumerSecret = Environment.GetEnvironmentVariable(consumerSecretKeyName); 28 | if (consumerSecret == null) 29 | { 30 | throw new Exception($"{consumerSecretKeyName} enviroment variable is not set."); 31 | } 32 | 33 | var accessToken = Environment.GetEnvironmentVariable(accessTokenKeyName); 34 | if (accessToken == null) 35 | { 36 | throw new Exception($"{accessTokenKeyName} enviroment variable is not set."); 37 | } 38 | 39 | var accessTokenSecret = Environment.GetEnvironmentVariable(accessTokenSecretKeyName); 40 | if (accessTokenSecret == null) 41 | { 42 | throw new Exception($"{accessTokenSecretKeyName} enviroment variable is not set."); 43 | } 44 | 45 | Console.Title = typeof(Program).Assembly.GetName().Name; 46 | 47 | return App.Run( 48 | nserviceBusConnectionString, 49 | track, 50 | consumerKey, 51 | consumerSecret, 52 | accessToken, 53 | accessTokenSecret, 54 | endpointName, 55 | analyzerAddress); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor/TweetMapper.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Twitter 2 | { 3 | using System.Linq; 4 | using HashBus.Twitter.Analyzer.Commands; 5 | using Tweetinvi.Core.Interfaces; 6 | 7 | static class TweetMapper 8 | { 9 | public static AnalyzeTweet Map(ITweet tweet, string track) 10 | { 11 | return new AnalyzeTweet 12 | { 13 | Tweet = MapTweet(tweet, track) 14 | }; 15 | } 16 | 17 | private static Tweet MapTweet(ITweet tweet, string track) 18 | { 19 | return tweet == null 20 | ? null 21 | : new Tweet 22 | { 23 | CreatedAt = tweet.CreatedAt, 24 | CreatedById = tweet.CreatedBy.Id, 25 | CreatedByIdStr = tweet.CreatedBy.IdStr, 26 | CreatedByName = tweet.CreatedBy.Name, 27 | CreatedByScreenName = tweet.CreatedBy.ScreenName, 28 | Hashtags = tweet.Hashtags 29 | .Select(hashtag => 30 | new Hashtag 31 | { 32 | Text = hashtag.Text, 33 | Indices = hashtag.Indices, 34 | }) 35 | .ToList(), 36 | Id = tweet.Id, 37 | RetweetedTweet = MapTweet(tweet.RetweetedTweet, track), 38 | Text = tweet.Text, 39 | Track = track, 40 | UserMentions = tweet.UserMentions 41 | .Where(userMention => userMention.Id.HasValue) 42 | .Select(userMention => 43 | new UserMention 44 | { 45 | Id = userMention.Id.Value, 46 | IdStr = userMention.IdStr, 47 | Indices = userMention.Indices, 48 | Name = userMention.Name, 49 | ScreenName = userMention.ScreenName, 50 | }) 51 | .ToList(), 52 | }; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor/Writer.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particular/HashBus/08184628814c8521ba2e37c9f8ac96286202ab52/src/HashBus.Twitter.Monitor/Writer.cs -------------------------------------------------------------------------------- /src/HashBus.Twitter.Monitor/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/App.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using RestSharp; 9 | 10 | class App 11 | { 12 | public static async Task Run( 13 | string webApiBaseUrl, 14 | string track, 15 | int refreshInterval, 16 | bool showPercentages, 17 | int verticalPadding, 18 | int horizontalPadding, 19 | string[] views, 20 | int rotateInterval) 21 | { 22 | var client = new RestClient(webApiBaseUrl); 23 | 24 | var allViews = new Dictionary 25 | { 26 | { 27 | "MostHashtagged", 28 | MostHashtaggedLeaderBoardViewFactory.Create( 29 | track, refreshInterval, 30 | showPercentages, 31 | verticalPadding, 32 | horizontalPadding, 33 | client) 34 | }, 35 | { 36 | "MostMentioned", 37 | MostMentionedLeaderBoardViewFactory.Create( 38 | track, refreshInterval, 39 | showPercentages, 40 | verticalPadding, 41 | horizontalPadding, 42 | client) 43 | }, 44 | { 45 | "MostRetweeted", 46 | MostRetweetedLeaderBoardViewFactory.Create( 47 | track, refreshInterval, 48 | showPercentages, 49 | verticalPadding, 50 | horizontalPadding, 51 | client) 52 | }, 53 | { 54 | "TopRetweeters", 55 | TopRetweetersLeaderBoardViewFactory.Create( 56 | track, refreshInterval, 57 | showPercentages, 58 | verticalPadding, 59 | horizontalPadding, 60 | client) 61 | }, 62 | { 63 | "TopTweeters", 64 | TopTweetersLeaderBoardViewFactory.Create( 65 | track, refreshInterval, 66 | showPercentages, 67 | verticalPadding, 68 | horizontalPadding, 69 | client) 70 | }, 71 | { 72 | "TopTweetersRetweeters", 73 | TopTweetersRetweetersLeaderBoardViewFactory.Create( 74 | track, refreshInterval, 75 | showPercentages, 76 | verticalPadding, 77 | horizontalPadding, 78 | client) 79 | }, 80 | }; 81 | 82 | var invalidViews = views.Where(view => !allViews.ContainsKey(view)).ToList(); 83 | if (invalidViews.Any()) 84 | { 85 | throw new ArgumentException($"View(s) not found: {string.Join(", ", invalidViews)}.", nameof(views)); 86 | } 87 | 88 | while (true) 89 | { 90 | foreach (var view in views) 91 | { 92 | var cancellationTokenSource = new CancellationTokenSource(); 93 | var task = allViews[view].RunAsync(cancellationTokenSource.Token); 94 | await Task.Delay(rotateInterval); 95 | cancellationTokenSource.Cancel(); 96 | await task; 97 | } 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/ColorTokenExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using ColoredConsole; 6 | 7 | static class ColorTokenExtensions 8 | { 9 | public static IList Trim(this IEnumerable source, int maxWidth) 10 | { 11 | IList tokens = new List(source); 12 | while (tokens.Sum(token => token.Text.Length) > maxWidth) 13 | { 14 | tokens = tokens 15 | .Reverse() 16 | .SkipWhile(token => token.Text.Length == 0) 17 | .Reverse() 18 | .ToList(); 19 | 20 | var oldLastToken = tokens.Last(); 21 | var newLastToken = new ColorToken( 22 | oldLastToken.Text.Substring(0, oldLastToken.Text.Length - 1), 23 | oldLastToken.Color, 24 | oldLastToken.BackgroundColor); 25 | 26 | tokens = tokens.Take(tokens.Count - 1).ToList(); 27 | tokens.Add(newLastToken); 28 | } 29 | 30 | return tokens; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/ConsoleHelper.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using System; 4 | using System.Diagnostics; 5 | using System.Runtime.InteropServices; 6 | 7 | static class ConsoleHelper 8 | { 9 | internal static void MakeTopMost() 10 | { 11 | var currentWindowHandle = Process.GetCurrentProcess().MainWindowHandle; 12 | 13 | SetWindowPos(currentWindowHandle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS); 14 | } 15 | 16 | private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); 17 | private const UInt32 SWP_NOSIZE = 0x0001; 18 | private const UInt32 SWP_NOMOVE = 0x0002; 19 | private const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE; 20 | 21 | [DllImport("user32.dll")] 22 | [return: MarshalAs(UnmanagedType.Bool)] 23 | private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/HashBus.Viewer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/IRunAsync.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | internal interface IRunAsync 7 | { 8 | Task RunAsync(CancellationToken cancellationToken); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/IService.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using System.Threading.Tasks; 4 | 5 | interface IService 6 | { 7 | Task GetAsync(TKey key); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/LeaderboardService.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using System; 4 | using System.Globalization; 5 | using System.Net; 6 | using System.Runtime.Remoting; 7 | using System.Threading.Tasks; 8 | using LiteGuard; 9 | using Newtonsoft.Json; 10 | using RestSharp; 11 | 12 | class LeaderboardService : IService> 13 | { 14 | private readonly IRestClient client; 15 | private readonly string resource; 16 | 17 | public LeaderboardService(IRestClient client, string resource) 18 | { 19 | Guard.AgainstNullArgument(nameof(client), client); 20 | Guard.AgainstNullArgument(nameof(resource), resource); 21 | 22 | this.client = client; 23 | this.resource = resource; 24 | } 25 | 26 | public async Task> GetAsync(string key) 27 | { 28 | // see https://github.com/NancyFx/Nancy/issues/1154 29 | key = Uri.EscapeDataString(key.Replace("#", "해시")); 30 | var request = new RestRequest(string.Format(CultureInfo.InvariantCulture, this.resource, key)); 31 | var response = await this.client.ExecuteGetTaskAsync(request); 32 | 33 | if (response.StatusCode == 0) 34 | { 35 | throw new ServerException(response.ErrorMessage, response.ErrorException); 36 | } 37 | 38 | if (response.StatusCode != HttpStatusCode.OK) 39 | { 40 | throw new Exception($"The server returned: {(int)response.StatusCode} {response.StatusDescription}", response.ErrorException); 41 | } 42 | 43 | return JsonConvert.DeserializeObject>(response.Content); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/LeaderboardView.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using ColoredConsole; 9 | 10 | class LeaderboardView : IRunAsync where TEntry : WebApi.IEntry 11 | { 12 | private static readonly Dictionary movementTokens = 13 | new Dictionary 14 | { 15 | { int.MinValue, ">" }, 16 | { -1, "^" }, 17 | { 0, "=" }, 18 | { 1, "v" }, 19 | }; 20 | 21 | private static readonly Dictionary movementColors = 22 | new Dictionary 23 | { 24 | { int.MinValue, ConsoleColor.Yellow }, 25 | { -1, ConsoleColor.Green}, 26 | { 0, ConsoleColor.Gray }, 27 | { 1, ConsoleColor.Red }, 28 | }; 29 | 30 | private static readonly Dictionary movementBackgroundColors = 31 | new Dictionary 32 | { 33 | { int.MinValue, ConsoleColor.DarkYellow }, 34 | { -1, ConsoleColor.DarkGreen }, 35 | { 0, ConsoleColor.Black }, 36 | { 1, ConsoleColor.DarkRed }, 37 | }; 38 | 39 | private readonly string track; 40 | private readonly int refreshInterval; 41 | private readonly IService> leaderboards; 42 | private readonly bool showPercentages; 43 | private readonly int verticalPadding; 44 | private readonly int horizontalPadding; 45 | private readonly Func matchEntries; 46 | private readonly Func> getText; 47 | private readonly string name; 48 | private readonly string itemsName; 49 | 50 | private WebApi.Leaderboard previousLeaderboard = new WebApi.Leaderboard(); 51 | 52 | public LeaderboardView( 53 | string track, 54 | int refreshInterval, 55 | IService> leaderboards, 56 | bool showPercentages, 57 | int verticalPadding, 58 | int horizontalPadding, 59 | Func matchEntries, 60 | Func> getText, 61 | string name, 62 | string itemsName) 63 | { 64 | this.track = track; 65 | this.refreshInterval = refreshInterval; 66 | this.leaderboards = leaderboards; 67 | this.showPercentages = showPercentages; 68 | this.verticalPadding = verticalPadding; 69 | this.horizontalPadding = horizontalPadding; 70 | this.matchEntries = matchEntries; 71 | this.getText = getText; 72 | this.name = name; 73 | this.itemsName = itemsName; 74 | } 75 | 76 | public Task RunAsync() 77 | { 78 | return this.RunAsync(new CancellationTokenSource().Token); 79 | } 80 | 81 | public async Task RunAsync(CancellationToken cancellationToken) 82 | { 83 | Console.CursorVisible = false; 84 | while (!cancellationToken.IsCancellationRequested) 85 | { 86 | WebApi.Leaderboard currentLeaderboard; 87 | try 88 | { 89 | currentLeaderboard = await leaderboards.GetAsync(track); 90 | } 91 | catch (Exception) 92 | { 93 | currentLeaderboard = previousLeaderboard; 94 | } 95 | 96 | var lines = new List>(); 97 | foreach (var currentEntry in currentLeaderboard?.Entries ?? 98 | Enumerable.Empty()) 99 | { 100 | var previousEntry = (previousLeaderboard?.Entries ?? 101 | Enumerable.Empty()) 102 | .FirstOrDefault(e => matchEntries(e, currentEntry)); 103 | 104 | var movement = previousEntry == null 105 | ? int.MinValue 106 | : Math.Sign(currentEntry.Position - previousEntry.Position); 107 | 108 | var countMovement = Math.Sign(Math.Min(previousEntry?.Count - currentEntry.Count ?? 0, movement)); 109 | 110 | var tokens = new List 111 | { 112 | $"{movementTokens[movement]} {currentEntry.Position.ToString().PadLeft(2)}".Color(movementColors[movement]), 113 | }; 114 | 115 | tokens.AddRange(getText(currentEntry)); 116 | tokens.Add($" {currentEntry.Count:N0}".Color(movementColors[countMovement])); 117 | 118 | if (showPercentages) 119 | { 120 | tokens.Add($" ({currentEntry.Count / (double)currentLeaderboard.Count:P0})".DarkGray()); 121 | } 122 | 123 | var maxWidth = Console.WindowWidth - (horizontalPadding * 2); 124 | tokens.Add(new string(' ', Math.Max(0, maxWidth - tokens.Sum(token => token.Text.Length)))); 125 | 126 | lines.Add(tokens.Trim(maxWidth).Select(token => token.On(movementBackgroundColors[movement]))); 127 | } 128 | 129 | Console.Clear(); 130 | for (var newLine = verticalPadding - 1; newLine >= 0; newLine--) 131 | { 132 | ColorConsole.WriteLine(); 133 | } 134 | 135 | var padding = new string(' ', horizontalPadding); 136 | ColorConsole.Write( 137 | padding, 138 | $" {track} ".DarkCyan().On(ConsoleColor.White), 139 | " ", 140 | name.White()); 141 | 142 | if (currentLeaderboard == previousLeaderboard) 143 | { 144 | ColorConsole.Write(" ", currentLeaderboard == previousLeaderboard ? " Service unavailable ".Yellow().OnRed() : ""); 145 | } 146 | 147 | ColorConsole.WriteLine(); 148 | 149 | ColorConsole.WriteLine( 150 | padding, 151 | "Powered by ".DarkGray(), 152 | " NServiceBus ".White().OnDarkBlue(), 153 | " from ".DarkGray(), 154 | "Particular Software".White()); 155 | 156 | ColorConsole.WriteLine(); 157 | foreach (var line in lines) 158 | { 159 | ColorConsole.WriteLine(new ColorToken[] { padding }.Concat(line).ToArray()); 160 | } 161 | 162 | ColorConsole.WriteLine(); 163 | 164 | var totalColor = currentLeaderboard?.Count - previousLeaderboard?.Count > 0 ? movementColors[-1] : movementColors[0]; 165 | ColorConsole.Write(padding, $"{currentLeaderboard?.Count ?? 0:N0} ".Color(totalColor), $"{itemsName}".DarkGray()); 166 | 167 | if (currentLeaderboard.Since.HasValue) 168 | { 169 | ColorConsole.WriteLine( 170 | $" since ".DarkGray(), 171 | $"{currentLeaderboard.Since?.ToLocalTime():dddd} {currentLeaderboard.Since?.ToLocalTime():HH:mm}".Gray()); 172 | } 173 | 174 | var maxMessageLength = 0; 175 | var refreshTime = DateTime.UtcNow.AddMilliseconds(refreshInterval); 176 | using (var timer = new Timer(c => 177 | { 178 | var timeLeft = new TimeSpan(0, 0, 0, (int)Math.Round((refreshTime - DateTime.UtcNow).TotalSeconds)); 179 | if (timeLeft.TotalSeconds == 0) 180 | { 181 | return; 182 | } 183 | 184 | var tokens = new[] 185 | { 186 | $"\r{padding}github.com/Particular/HashBus".Cyan(), 187 | $" · Refreshing in {timeLeft.TotalSeconds}...".DarkGray(), 188 | }; 189 | 190 | var currentLength = tokens.Sum(x => x.Text.Length); 191 | maxMessageLength = Math.Max(maxMessageLength, currentLength); 192 | tokens = tokens.Concat(new ColorToken[] { new string(' ', maxMessageLength - currentLength) }).ToArray(); 193 | ColorConsole.Write(tokens); 194 | })) 195 | { 196 | timer.Change(0, 1000); 197 | await Task.Delay(refreshInterval); 198 | } 199 | 200 | previousLeaderboard = currentLeaderboard; 201 | } 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/MostHashtaggedLeaderBoardViewFactory.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using System; 4 | using ColoredConsole; 5 | using HashBus.WebApi; 6 | using RestSharp; 7 | 8 | static class MostHashtaggedLeaderBoardViewFactory 9 | { 10 | public static LeaderboardView Create( 11 | string track, 12 | int refreshInterval, 13 | bool showPercentages, 14 | int verticalPadding, 15 | int horizontalPadding, 16 | IRestClient client) 17 | { 18 | return new LeaderboardView( 19 | track, 20 | refreshInterval, 21 | new LeaderboardService(client, "/most-hashtagged/{0}"), 22 | showPercentages, 23 | verticalPadding, 24 | horizontalPadding, 25 | (entry1, entry2) => string.Equals(entry1.Text, entry2.Text, StringComparison.InvariantCultureIgnoreCase), 26 | entry => new[] { $" #{entry.Text}".Cyan(), }, 27 | "Most Hashtagged", 28 | "hashtag usages"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/MostMentionedLeaderBoardViewFactory.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using ColoredConsole; 4 | using HashBus.WebApi; 5 | using RestSharp; 6 | 7 | static class MostMentionedLeaderBoardViewFactory 8 | { 9 | public static LeaderboardView Create( 10 | string track, 11 | int refreshInterval, 12 | bool showPercentages, 13 | int verticalPadding, 14 | int horizontalPadding, 15 | IRestClient client) 16 | { 17 | return new LeaderboardView( 18 | track, 19 | refreshInterval, 20 | new LeaderboardService(client, "/most-mentioned/{0}"), 21 | showPercentages, 22 | verticalPadding, 23 | horizontalPadding, 24 | (entry1, entry2) => entry1.Id == entry2.Id, 25 | entry => new[] { $" {entry.Name}".White(), $" @{entry.ScreenName}".Cyan(), }, 26 | "Most Mentioned", 27 | "mentions"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/MostRetweetedLeaderBoardViewFactory.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using ColoredConsole; 4 | using HashBus.WebApi; 5 | using RestSharp; 6 | 7 | static class MostRetweetedLeaderBoardViewFactory 8 | { 9 | public static LeaderboardView Create( 10 | string track, 11 | int refreshInterval, 12 | bool showPercentages, 13 | int verticalPadding, 14 | int horizontalPadding, 15 | IRestClient client) 16 | { 17 | return new LeaderboardView( 18 | track, 19 | refreshInterval, 20 | new LeaderboardService(client, "/most-retweeted/{0}"), 21 | showPercentages, 22 | verticalPadding, 23 | horizontalPadding, 24 | (entry1, entry2) => entry1.Id == entry2.Id, 25 | entry => new[] { $" {entry.Name}".White(), $" @{entry.ScreenName}".Cyan(), }, 26 | "Most Retweeted", 27 | "retweets"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using System; 4 | using System.Configuration; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | class Program 10 | { 11 | static Task Main() 12 | { 13 | var webApiBaseUrl = ConfigurationManager.AppSettings["WebApiBaseUrl"]; 14 | var track = ConfigurationManager.AppSettings["Track"]; 15 | var refreshInterval = int.Parse(ConfigurationManager.AppSettings["refreshInterval"]); 16 | var showPercentages = bool.Parse(ConfigurationManager.AppSettings["ShowPercentages"]); 17 | var verticalPadding = int.Parse(ConfigurationManager.AppSettings["VerticalPadding"]); 18 | var horizontalPadding = int.Parse(ConfigurationManager.AppSettings["HorizontalPadding"]); 19 | var rotateInterval = int.Parse(ConfigurationManager.AppSettings["RotateInterval"]); 20 | var views = ConfigurationManager.AppSettings["Views"] 21 | .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) 22 | .Select(view => view.Trim()) 23 | .ToArray(); 24 | 25 | Console.OutputEncoding = Encoding.UTF8; 26 | Console.Title = typeof(Program).Assembly.GetName().Name; 27 | ConsoleHelper.MakeTopMost(); 28 | 29 | return App.Run( 30 | webApiBaseUrl, 31 | track, 32 | refreshInterval, 33 | showPercentages, 34 | verticalPadding, 35 | horizontalPadding, 36 | views, 37 | rotateInterval); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/TopRetweetersLeaderBoardViewFactory.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using ColoredConsole; 4 | using HashBus.WebApi; 5 | using RestSharp; 6 | 7 | static class TopRetweetersLeaderBoardViewFactory 8 | { 9 | public static LeaderboardView Create( 10 | string track, 11 | int refreshInterval, 12 | bool showPercentages, 13 | int verticalPadding, 14 | int horizontalPadding, 15 | IRestClient client) 16 | { 17 | return new LeaderboardView( 18 | track, 19 | refreshInterval, 20 | new LeaderboardService(client, "/top-retweeters/{0}"), 21 | showPercentages, 22 | verticalPadding, 23 | horizontalPadding, 24 | (entry1, entry2) => entry1.Id == entry2.Id, 25 | entry => new[] { $" {entry.Name}".White(), $" @{entry.ScreenName}".Cyan(), }, 26 | "Top Retweeters", 27 | "retweets"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/TopTweetersLeaderBoardViewFactory.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using ColoredConsole; 4 | using HashBus.WebApi; 5 | using RestSharp; 6 | 7 | static class TopTweetersLeaderBoardViewFactory 8 | { 9 | public static LeaderboardView Create( 10 | string track, 11 | int refreshInterval, 12 | bool showPercentages, 13 | int verticalPadding, 14 | int horizontalPadding, 15 | IRestClient client) 16 | { 17 | return new LeaderboardView( 18 | track, 19 | refreshInterval, 20 | new LeaderboardService(client, "/top-tweeters/{0}"), 21 | showPercentages, 22 | verticalPadding, 23 | horizontalPadding, 24 | (entry1, entry2) => entry1.Id == entry2.Id, 25 | entry => new[] { $" {entry.Name}".White(), $" @{entry.ScreenName}".Cyan(), }, 26 | "Top Tweeters", 27 | "tweets"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/TopTweetersRetweetersLeaderBoardViewFactory.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.Viewer 2 | { 3 | using ColoredConsole; 4 | using HashBus.WebApi; 5 | using RestSharp; 6 | 7 | static class TopTweetersRetweetersLeaderBoardViewFactory 8 | { 9 | public static LeaderboardView Create( 10 | string track, 11 | int refreshInterval, 12 | bool showPercentages, 13 | int verticalPadding, 14 | int horizontalPadding, 15 | IRestClient client) 16 | { 17 | return new LeaderboardView( 18 | track, 19 | refreshInterval, 20 | new LeaderboardService(client, "/top-tweeters-retweeters/{0}"), 21 | showPercentages, 22 | verticalPadding, 23 | horizontalPadding, 24 | (entry1, entry2) => entry1.Id == entry2.Id, 25 | entry => new[] { $" {entry.Name}".White(), $" @{entry.ScreenName}".Cyan(), }, 26 | "Top Tweeters/Retweeters", 27 | "tweets/retweets"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/HashBus.Viewer/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/App.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading; 7 | using ColoredConsole; 8 | using HashBus.ReadModel; 9 | using HashBus.ReadModel.MongoDB; 10 | using MongoDB.Driver; 11 | using Nancy; 12 | using Nancy.Bootstrapper; 13 | using Nancy.Hosting.Self; 14 | using Nancy.Responses.Negotiation; 15 | using Nancy.TinyIoc; 16 | 17 | class App 18 | { 19 | public static void Run( 20 | Uri baseUri, string mongoConnectionString, string mongoDBDatabase, IEnumerable ignoredUserNames, IEnumerable ignoredHashtags) 21 | { 22 | var bootstrapper = new Bootstrapper( 23 | new MongoClient(mongoConnectionString).GetDatabase(mongoDBDatabase), 24 | new IgnoredUserNamesService(ignoredUserNames), 25 | new IgnoredHashtagsService(ignoredHashtags)); 26 | 27 | var hostConfiguration = new HostConfiguration 28 | { 29 | UrlReservations = new UrlReservations { CreateAutomatically = true }, 30 | }; 31 | 32 | using (var host = new NancyHost(bootstrapper, hostConfiguration, baseUri)) 33 | { 34 | host.Start(); 35 | ColorConsole.WriteLine("Web API hosted at ".Gray(), $"{baseUri}".White()); 36 | ColorConsole.Write("Powered by ".DarkGray(), " Nancy ".Black().OnWhite()); 37 | Thread.Sleep(Timeout.Infinite); 38 | } 39 | } 40 | 41 | private class Bootstrapper : DefaultNancyBootstrapper 42 | { 43 | private readonly IMongoDatabase mongoDatabase; 44 | private readonly IIgnoredUserNamesService ignoredUserNamesService; 45 | private readonly IIgnoredHashtagsService ignoredHashtagsService; 46 | 47 | public Bootstrapper(IMongoDatabase mongoDatabase, IIgnoredUserNamesService ignoredUserNamesService, IIgnoredHashtagsService ignoredHashtagsService) 48 | { 49 | this.mongoDatabase = mongoDatabase; 50 | this.ignoredUserNamesService = ignoredUserNamesService; 51 | this.ignoredHashtagsService = ignoredHashtagsService; 52 | } 53 | 54 | protected override NancyInternalConfiguration InternalConfiguration 55 | { 56 | get 57 | { 58 | return NancyInternalConfiguration.WithOverrides(configuration => 59 | configuration.ResponseProcessors = new[] { typeof(JsonProcessor) }); 60 | } 61 | } 62 | 63 | protected override void ConfigureApplicationContainer(TinyIoCContainer container) 64 | { 65 | base.ConfigureApplicationContainer(container); 66 | 67 | container.Register(this.ignoredUserNamesService); 68 | container.Register(this.ignoredHashtagsService); 69 | 70 | container.Register>>( 71 | new MongoDBListRepository(this.mongoDatabase, "most_mentioned__mentions")); 72 | 73 | container.Register>>( 74 | new MongoDBListRepository(this.mongoDatabase, "top_tweeters__tweets")); 75 | 76 | container.Register>>( 77 | new MongoDBListRepository(this.mongoDatabase, "top_tweeters_retweeters__tweets_retweets")); 78 | 79 | container.Register>>( 80 | new MongoDBListRepository(this.mongoDatabase, "top_retweeters__retweets")); 81 | 82 | container.Register>>( 83 | new MongoDBListRepository(this.mongoDatabase, "most_retweeted__retweetees")); 84 | 85 | container.Register>>( 86 | new MongoDBListRepository(this.mongoDatabase, "most_hashtagged__hashtags")); 87 | } 88 | 89 | protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context) 90 | { 91 | base.RequestStartup(container, pipelines, context); 92 | 93 | pipelines.AfterRequest.AddItemToEndOfPipeline(ctx => 94 | { 95 | if (ctx.Request.Headers.Keys.Contains("Origin")) 96 | { 97 | ctx.Response.Headers["Access-Control-Allow-Origin"] = 98 | string.Join(" ", ctx.Request.Headers["Origin"]); 99 | 100 | if (ctx.Request.Method == "OPTIONS") 101 | { 102 | ctx.Response.Headers["Access-Control-Allow-Methods"] = 103 | "GET, POST, PUT, DELETE, OPTIONS"; 104 | 105 | if (ctx.Request.Headers.Keys.Contains("Access-Control-Request-Headers")) 106 | { 107 | ctx.Response.Headers["Access-Control-Allow-Headers"] = 108 | string.Join(", ", ctx.Request.Headers["Access-Control-Request-Headers"]); 109 | } 110 | } 111 | } 112 | }); 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/HashBus.WebApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/HashtagEntry.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | public class HashtagEntry : IEntry 4 | { 5 | public int Position { get; set; } 6 | 7 | public string Text { get; set; } 8 | 9 | public int Count { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/IEntry.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | public interface IEntry 4 | { 5 | int Count { get; set; } 6 | 7 | int Position { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/IIgnoredHashtagsService.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | using System.Collections.Generic; 4 | 5 | public interface IIgnoredHashtagsService 6 | { 7 | IReadOnlyList Get(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/IIgnoredUserNamesService.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | using System.Collections.Generic; 4 | 5 | public interface IIgnoredUserNamesService 6 | { 7 | IReadOnlyList Get(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/IgnoredHashtagsService.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using LiteGuard; 6 | 7 | class IgnoredHashtagsService : IIgnoredHashtagsService 8 | { 9 | private readonly List ignoredHashtags; 10 | 11 | public IgnoredHashtagsService(IEnumerable ignoredHashtags) 12 | { 13 | Guard.AgainstNullArgument(nameof(ignoredHashtags), ignoredHashtags); 14 | 15 | this.ignoredHashtags = ignoredHashtags.ToList(); 16 | } 17 | 18 | public IReadOnlyList Get() 19 | { 20 | return this.ignoredHashtags.ToList(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/IgnoredUserNamesService.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using LiteGuard; 6 | 7 | class IgnoredUserNamesService : IIgnoredUserNamesService 8 | { 9 | private readonly List ignoredUserNames; 10 | 11 | public IgnoredUserNamesService(IEnumerable ignoredUserNames) 12 | { 13 | Guard.AgainstNullArgument(nameof(ignoredUserNames), ignoredUserNames); 14 | 15 | this.ignoredUserNames = ignoredUserNames.ToList(); 16 | } 17 | 18 | public IReadOnlyList Get() 19 | { 20 | return this.ignoredUserNames.ToList(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/Leaderboard.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | class Leaderboard 7 | { 8 | public IList Entries { get; set; } 9 | 10 | public int Count { get; set; } 11 | 12 | public DateTime? Since { get; set; } 13 | 14 | public DateTime? LastActivityDateTime { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/MostHashtaggedModule.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using HashBus.ReadModel; 7 | using Nancy; 8 | 9 | public class MostHashtaggedModule : NancyModule 10 | { 11 | public MostHashtaggedModule(IRepository> hashtags, IIgnoredHashtagsService ignoredHashtagsService) 12 | { 13 | this.Get["/most-hashtagged/{track}", true] = async (parameters, __) => 14 | { 15 | // see https://github.com/NancyFx/Nancy/issues/1154 16 | var track = ((string)parameters.track).Replace("해시", "#"); 17 | var trackHashtags = (await hashtags.GetAsync(track)).ToList(); 18 | var entries = trackHashtags 19 | .Where(item => !ignoredHashtagsService.Get().Contains(item.Text, StringComparer.OrdinalIgnoreCase)) 20 | .GroupBy(tweet => tweet.Text, StringComparer.InvariantCultureIgnoreCase) 21 | .Select(g => new HashtagEntry 22 | { 23 | Text = g.Key, 24 | Count = g.Count(), 25 | }) 26 | .OrderByDescending(entry => entry.Count) 27 | .Select((entry, index) => 28 | { 29 | entry.Position = index + 1; 30 | return entry; 31 | }) 32 | .Take(10) 33 | .ToList(); 34 | 35 | return new Leaderboard 36 | { 37 | Entries = entries, 38 | Count = trackHashtags.Count, 39 | Since = trackHashtags.Any() ? trackHashtags.Min(hashtag => hashtag.HashtaggedAt) : (DateTime?)null, 40 | LastActivityDateTime = trackHashtags.Any() ? trackHashtags.Max(hashtag => hashtag.HashtaggedAt) : (DateTime?)null, 41 | }; 42 | }; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/MostMentionedModule.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using HashBus.ReadModel; 7 | using Nancy; 8 | 9 | public class MostMentionedModule : NancyModule 10 | { 11 | public MostMentionedModule( 12 | IRepository> mentions, IIgnoredUserNamesService ignoredUserNamesService) 13 | { 14 | this.Get["/most-mentioned/{track}", true] = async (parameters, __) => 15 | { 16 | // see https://github.com/NancyFx/Nancy/issues/1154 17 | var track = ((string)parameters.track).Replace("해시", "#"); 18 | var trackMentions = (await mentions.GetAsync(track)).ToList(); 19 | var entries = trackMentions 20 | .Where(item => !ignoredUserNamesService.Get().Contains(item.UserMentionScreenName)) 21 | .GroupBy(mention => mention.UserMentionId) 22 | .Select(g => new UserEntry 23 | { 24 | Id = g.Key, 25 | Name = g.First().UserMentionName, 26 | ScreenName = g.First().UserMentionScreenName, 27 | Count = g.Count(), 28 | }) 29 | .OrderByDescending(entry => entry.Count) 30 | .Select((entry, index) => 31 | { 32 | entry.Position = index + 1; 33 | return entry; 34 | }) 35 | .Take(10) 36 | .ToList(); 37 | 38 | return new Leaderboard 39 | { 40 | Entries = entries, 41 | Count = trackMentions.Count, 42 | Since = trackMentions.Any() ? trackMentions.Min(mention => mention.MentionedAt) : (DateTime?) null, 43 | LastActivityDateTime = trackMentions.Any()? trackMentions.Max(mention => mention.MentionedAt): (DateTime?) null, 44 | }; 45 | }; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/MostRetweetedModule.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using HashBus.ReadModel; 7 | using Nancy; 8 | 9 | public class MostRetweetedModule : NancyModule 10 | { 11 | public MostRetweetedModule( 12 | IRepository> tweets, IIgnoredUserNamesService ignoredUserNamesService) 13 | { 14 | this.Get["/most-retweeted/{track}", true] = async (parameters, __) => 15 | { 16 | // see https://github.com/NancyFx/Nancy/issues/1154 17 | var track = ((string)parameters.track).Replace("해시", "#"); 18 | var trackTweets = (await tweets.GetAsync(track)).ToList(); 19 | var entries = trackTweets 20 | .Where(item => !ignoredUserNamesService.Get().Contains(item.UserScreenName)) 21 | .GroupBy(tweet => tweet.UserId) 22 | .Select(g => new UserEntry 23 | { 24 | Id = g.Key, 25 | Name = g.First().UserName, 26 | ScreenName = g.First().UserScreenName, 27 | Count = g.Count(), 28 | }) 29 | .OrderByDescending(entry => entry.Count) 30 | .Select((entry, index) => 31 | { 32 | entry.Position = index + 1; 33 | return entry; 34 | }) 35 | .Take(10) 36 | .ToList(); 37 | 38 | return new Leaderboard 39 | { 40 | Entries = entries, 41 | Count = trackTweets.Count, 42 | Since = trackTweets.Any() ? trackTweets.Min(tweet => tweet.RetweetedAt) : (DateTime?)null, 43 | LastActivityDateTime = trackTweets.Any() ? trackTweets.Max(tweet => tweet.RetweetedAt) : (DateTime?)null, 44 | }; 45 | }; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | using System; 4 | using System.Configuration; 5 | using System.Linq; 6 | 7 | class Program 8 | { 9 | static void Main() 10 | { 11 | var baseUri = new Uri(ConfigurationManager.AppSettings["BaseUri"]); 12 | var mongoConnectionString = ConfigurationManager.AppSettings["MongoConnectionString"]; 13 | var mongoDBDatabase = ConfigurationManager.AppSettings["MongoDBDatabase"]; 14 | var ignoredUserNames = ConfigurationManager.AppSettings["IgnoredUserNames"] 15 | .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) 16 | .Select(userName => userName.Trim()); 17 | 18 | var ignoredHashtags = ConfigurationManager.AppSettings["IgnoredHashtags"] 19 | .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) 20 | .Select(hashtag => hashtag.Trim()); 21 | 22 | Console.Title = typeof(Program).Assembly.GetName().Name; 23 | 24 | App.Run(baseUri, mongoConnectionString, mongoDBDatabase, ignoredUserNames, ignoredHashtags); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/TopRetweetersModule.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using HashBus.ReadModel; 7 | using Nancy; 8 | 9 | public class TopRetweetersModule : NancyModule 10 | { 11 | public TopRetweetersModule 12 | (IRepository> tweets, IIgnoredUserNamesService ignoredUserNamesService) 13 | { 14 | this.Get["/top-retweeters/{track}", true] = async (parameters, __) => 15 | { 16 | // see https://github.com/NancyFx/Nancy/issues/1154 17 | var track = ((string)parameters.track).Replace("해시", "#"); 18 | var trackTweets = (await tweets.GetAsync(track)).ToList(); 19 | var entries = trackTweets 20 | .Where(item => !ignoredUserNamesService.Get().Contains(item.UserScreenName)) 21 | .GroupBy(tweet => tweet.UserId) 22 | .Select(g => new UserEntry 23 | { 24 | Id = g.Key, 25 | Name = g.First().UserName, 26 | ScreenName = g.First().UserScreenName, 27 | Count = g.Count(), 28 | }) 29 | .OrderByDescending(entry => entry.Count) 30 | .Select((entry, index) => 31 | { 32 | entry.Position = index + 1; 33 | return entry; 34 | }) 35 | .Take(10) 36 | .ToList(); 37 | 38 | return new Leaderboard 39 | { 40 | Entries = entries, 41 | Count = trackTweets.Count, 42 | Since = trackTweets.Any() ? trackTweets.Min(tweet => tweet.RetweetedAt) : (DateTime?)null, 43 | LastActivityDateTime = trackTweets.Any() ? trackTweets.Max(tweet => tweet.RetweetedAt) : (DateTime?)null, 44 | }; 45 | }; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/TopTweetersModule.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using HashBus.ReadModel; 7 | using Nancy; 8 | 9 | public class TopTweetersModule : NancyModule 10 | { 11 | public TopTweetersModule( 12 | IRepository> tweets, IIgnoredUserNamesService ignoredUserNamesService) 13 | { 14 | this.Get["/top-tweeters/{track}", true] = async (parameters, __) => 15 | { 16 | // see https://github.com/NancyFx/Nancy/issues/1154 17 | var track = ((string)parameters.track).Replace("해시", "#"); 18 | var trackTweets = (await tweets.GetAsync(track)).ToList(); 19 | var entries = trackTweets 20 | .Where(item => !ignoredUserNamesService.Get().Contains(item.UserScreenName)) 21 | .GroupBy(tweet => tweet.UserId) 22 | .Select(g => new UserEntry 23 | { 24 | Id = g.Key, 25 | Name = g.First().UserName, 26 | ScreenName = g.First().UserScreenName, 27 | Count = g.Count(), 28 | }) 29 | .OrderByDescending(entry => entry.Count) 30 | .Select((entry, index) => 31 | { 32 | entry.Position = index + 1; 33 | return entry; 34 | }) 35 | .Take(10) 36 | .ToList(); 37 | 38 | return new Leaderboard 39 | { 40 | Entries = entries, 41 | Count = trackTweets.Count, 42 | Since = trackTweets.Any() ? trackTweets.Min(tweet => tweet.TweetedAt) : (DateTime?)null, 43 | LastActivityDateTime = trackTweets.Any() ? trackTweets.Max(tweet => tweet.TweetedAt) : (DateTime?)null, 44 | }; 45 | }; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/TopTweetersRetweetersModule.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using HashBus.ReadModel; 7 | using Nancy; 8 | 9 | public class TopTweetersRetweetersModule : NancyModule 10 | { 11 | public TopTweetersRetweetersModule( 12 | IRepository> tweets, IIgnoredUserNamesService ignoredUserNamesService) 13 | { 14 | this.Get["/top-tweeters-retweeters/{track}", true] = async (parameters, __) => 15 | { 16 | // see https://github.com/NancyFx/Nancy/issues/1154 17 | var track = ((string)parameters.track).Replace("해시", "#"); 18 | var trackTweets = (await tweets.GetAsync(track)).ToList(); 19 | var entries = trackTweets 20 | .Where(item => !ignoredUserNamesService.Get().Contains(item.UserScreenName)) 21 | .GroupBy(tweet => tweet.UserId) 22 | .Select(g => new UserEntry 23 | { 24 | Id = g.Key, 25 | Name = g.First().UserName, 26 | ScreenName = g.First().UserScreenName, 27 | Count = g.Count(), 28 | }) 29 | .OrderByDescending(entry => entry.Count) 30 | .Select((entry, index) => 31 | { 32 | entry.Position = index + 1; 33 | return entry; 34 | }) 35 | .Take(10) 36 | .ToList(); 37 | 38 | return new Leaderboard 39 | { 40 | Entries = entries, 41 | Count = trackTweets.Count, 42 | Since = trackTweets.Any() ? trackTweets.Min(tweet => tweet.TweetedRetweetedAt) : (DateTime?)null, 43 | LastActivityDateTime = trackTweets.Any() ? trackTweets.Max(tweet => tweet.TweetedRetweetedAt) : (DateTime?)null, 44 | }; 45 | }; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/UserEntry.cs: -------------------------------------------------------------------------------- 1 | namespace HashBus.WebApi 2 | { 3 | public class UserEntry : IEntry 4 | { 5 | public int Position { get; set; } 6 | 7 | public long? Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public string ScreenName { get; set; } 12 | 13 | public int Count { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/HashBus.WebApi/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------